diff --git a/Android.mk b/Android.mk index 00655297fbb35b8518b0abb838147b3bf02d4b62..166db4ee016bbb5422245fec2118786830d941d1 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,10 @@ LOCAL_PATH := $(call my-dir) # TODO: find a more appropriate way to do this. framework_res_source_path := APPS/framework-res_intermediates/src -# the library +# Build the master framework library. +# The framework contains too many method references (>64K) for poor old DEX. +# So we first build the framework as a monolithic static library then split it +# up into smaller pieces. # ============================================================ include $(CLEAR_VARS) @@ -39,14 +42,6 @@ LOCAL_SRC_FILES += \ core/java/android/speech/tts/EventLogTags.logtags \ core/java/android/webkit/EventLogTags.logtags \ -# The following filters out code we are temporarily not including at all. -# TODO: Move AWT and beans (and associated harmony code) back into libcore. -# TODO: Maybe remove javax.microedition entirely? -# TODO: Move SyncML (org.mobilecontrol.*) into its own library. -LOCAL_SRC_FILES := $(filter-out \ - org/mobilecontrol/% \ - ,$(LOCAL_SRC_FILES)) - ## READ ME: ######################################################## ## ## When updating this list of aidl files, consider if that aidl is @@ -100,6 +95,7 @@ LOCAL_SRC_FILES += \ core/java/android/bluetooth/IBluetoothManager.aidl \ core/java/android/bluetooth/IBluetoothManagerCallback.aidl \ core/java/android/bluetooth/IBluetoothPbap.aidl \ + core/java/android/bluetooth/IBluetoothMap.aidl \ core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \ core/java/android/bluetooth/IBluetoothGatt.aidl \ core/java/android/bluetooth/IBluetoothGattCallback.aidl \ @@ -109,6 +105,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/IIntentReceiver.aidl \ core/java/android/content/IIntentSender.aidl \ core/java/android/content/IOnPrimaryClipChangedListener.aidl \ + core/java/android/content/IAnonymousSyncAdapter.aidl \ core/java/android/content/ISyncAdapter.aidl \ core/java/android/content/ISyncContext.aidl \ core/java/android/content/ISyncStatusObserver.aidl \ @@ -119,11 +116,22 @@ LOCAL_SRC_FILES += \ core/java/android/content/pm/IPackageMoveObserver.aidl \ core/java/android/content/pm/IPackageStatsObserver.aidl \ core/java/android/database/IContentObserver.aidl \ + core/java/android/hardware/ICameraService.aidl \ + core/java/android/hardware/ICameraServiceListener.aidl \ + core/java/android/hardware/ICamera.aidl \ + core/java/android/hardware/ICameraClient.aidl \ + core/java/android/hardware/IConsumerIrService.aidl \ + core/java/android/hardware/IProCameraUser.aidl \ + core/java/android/hardware/IProCameraCallbacks.aidl \ + core/java/android/hardware/camera2/ICameraDeviceUser.aidl \ + core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl \ core/java/android/hardware/ISerialManager.aidl \ core/java/android/hardware/display/IDisplayManager.aidl \ core/java/android/hardware/display/IDisplayManagerCallback.aidl \ core/java/android/hardware/input/IInputManager.aidl \ core/java/android/hardware/input/IInputDevicesChangedListener.aidl \ + core/java/android/hardware/location/IFusedLocationHardware.aidl \ + core/java/android/hardware/location/IFusedLocationHardwareSink.aidl \ core/java/android/hardware/location/IGeofenceHardware.aidl \ core/java/android/hardware/location/IGeofenceHardwareCallback.aidl \ core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \ @@ -135,10 +143,13 @@ LOCAL_SRC_FILES += \ core/java/android/net/INetworkStatsService.aidl \ core/java/android/net/INetworkStatsSession.aidl \ core/java/android/net/nsd/INsdManager.aidl \ - core/java/android/nfc/INdefPushCallback.aidl \ + core/java/android/nfc/IAppCallback.aidl \ core/java/android/nfc/INfcAdapter.aidl \ core/java/android/nfc/INfcAdapterExtras.aidl \ core/java/android/nfc/INfcTag.aidl \ + core/java/android/nfc/INfcCardEmulation.aidl \ + core/java/android/os/IBatteryPropertiesListener.aidl \ + core/java/android/os/IBatteryPropertiesRegistrar.aidl \ core/java/android/os/ICancellationSignal.aidl \ core/java/android/os/IHardwareService.aidl \ core/java/android/os/IMessenger.aidl \ @@ -151,6 +162,17 @@ LOCAL_SRC_FILES += \ core/java/android/os/IUserManager.aidl \ core/java/android/os/IVibratorService.aidl \ core/java/android/service/notification/INotificationListener.aidl \ + core/java/android/print/ILayoutResultCallback.aidl \ + core/java/android/print/IPrinterDiscoveryObserver.aidl \ + core/java/android/print/IPrintDocumentAdapter.aidl \ + core/java/android/print/IPrintJobStateChangeListener.aidl \ + core/java/android/print/IPrintManager.aidl \ + core/java/android/print/IPrintSpooler.aidl \ + core/java/android/print/IPrintSpoolerCallbacks.aidl \ + core/java/android/print/IPrintSpoolerClient.aidl \ + core/java/android/print/IWriteResultCallback.aidl \ + core/java/android/printservice/IPrintService.aidl \ + core/java/android/printservice/IPrintServiceClient.aidl \ core/java/android/service/dreams/IDreamManager.aidl \ core/java/android/service/dreams/IDreamService.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ @@ -161,6 +183,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/accessibility/IAccessibilityManager.aidl \ core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \ core/java/android/view/IApplicationToken.aidl \ + core/java/android/view/IAssetAtlas.aidl \ core/java/android/view/IMagnificationCallbacks.aidl \ core/java/android/view/IInputFilter.aidl \ core/java/android/view/IInputFilterHost.aidl \ @@ -178,6 +201,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/app/IAppOpsCallback.aidl \ core/java/com/android/internal/app/IAppOpsService.aidl \ core/java/com/android/internal/app/IBatteryStats.aidl \ + core/java/com/android/internal/app/IProcessStats.aidl \ core/java/com/android/internal/app/IUsageStats.aidl \ core/java/com/android/internal/app/IMediaContainerService.aidl \ core/java/com/android/internal/appwidget/IAppWidgetService.aidl \ @@ -186,6 +210,9 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/backup/IObbBackupService.aidl \ core/java/com/android/internal/policy/IFaceLockCallback.aidl \ core/java/com/android/internal/policy/IFaceLockInterface.aidl \ + core/java/com/android/internal/policy/IKeyguardShowCallback.aidl \ + core/java/com/android/internal/policy/IKeyguardExitCallback.aidl \ + core/java/com/android/internal/policy/IKeyguardService.aidl \ core/java/com/android/internal/os/IDropBoxManagerService.aidl \ core/java/com/android/internal/os/IResultReceiver.aidl \ core/java/com/android/internal/statusbar/IStatusBar.aidl \ @@ -209,12 +236,14 @@ LOCAL_SRC_FILES += \ keystore/java/android/security/IKeyChainService.aidl \ location/java/android/location/ICountryDetector.aidl \ location/java/android/location/ICountryListener.aidl \ + location/java/android/location/IFusedProvider.aidl \ location/java/android/location/IGeocodeProvider.aidl \ location/java/android/location/IGeofenceProvider.aidl \ location/java/android/location/IGpsStatusListener.aidl \ location/java/android/location/IGpsStatusProvider.aidl \ location/java/android/location/ILocationListener.aidl \ location/java/android/location/ILocationManager.aidl \ + location/java/android/location/IFusedGeofenceHardware.aidl \ location/java/android/location/IGpsGeofenceHardware.aidl \ location/java/android/location/INetInitiatedListener.aidl \ location/java/com/android/internal/location/ILocationProvider.aidl \ @@ -234,9 +263,10 @@ LOCAL_SRC_FILES += \ telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ - wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl -# - + wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ + packages/services/PacProcessor/com/android/net/IProxyService.aidl \ + packages/services/Proxy/com/android/net/IProxyCallback.aidl \ + packages/services/Proxy/com/android/net/IProxyPortListener.aidl \ # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) @@ -249,32 +279,65 @@ LOCAL_INTERMEDIATE_SOURCES := \ LOCAL_NO_STANDARD_LIBRARIES := true LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt core core-junit ext okhttp +LOCAL_MODULE := framework-base + +LOCAL_JAR_EXCLUDE_FILES := none + +include $(BUILD_STATIC_JAVA_LIBRARY) + +# Make sure that R.java and Manifest.java are built before we build +# the source for this library. +framework_res_R_stamp := \ + $(call intermediates-dir-for,APPS,framework-res,,COMMON)/src/R.stamp +$(full_classes_compiled_jar): $(framework_res_R_stamp) + +# Build part 1 of the framework library. +# ============================================================ +include $(CLEAR_VARS) + LOCAL_MODULE := framework LOCAL_MODULE_CLASS := JAVA_LIBRARIES +LOCAL_NO_STANDARD_LIBRARIES := true +LOCAL_STATIC_JAVA_LIBRARIES := framework-base +LOCAL_DX_FLAGS := --core-library + +# Packages to include, use \* wildcard to include descendants. +LOCAL_JAR_PACKAGES := android\* # List of classes and interfaces which should be loaded by the Zygote. LOCAL_JAVA_RESOURCE_FILES += $(LOCAL_PATH)/preloaded-classes -#LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt +include $(BUILD_JAVA_LIBRARY) +framework_module := $(LOCAL_INSTALLED_MODULE) +# Build part 2 of the framework library. +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_MODULE := framework2 +LOCAL_MODULE_CLASS := JAVA_LIBRARIES +LOCAL_NO_STANDARD_LIBRARIES := true +LOCAL_STATIC_JAVA_LIBRARIES := framework-base LOCAL_DX_FLAGS := --core-library -include $(BUILD_JAVA_LIBRARY) +# Packages to include, use \* wildcard to include descendants. +LOCAL_JAR_PACKAGES := com\* javax\* -# Make sure that R.java and Manifest.java are built before we build -# the source for this library. -framework_res_R_stamp := \ - $(call intermediates-dir-for,APPS,framework-res,,COMMON)/src/R.stamp -$(full_classes_compiled_jar): $(framework_res_R_stamp) +include $(BUILD_JAVA_LIBRARY) +framework2_module := $(LOCAL_INSTALLED_MODULE) -# Make sure that framework-res is installed when framework is. -$(LOCAL_INSTALLED_MODULE): | $(dir $(LOCAL_INSTALLED_MODULE))framework-res.apk +# Make sure that all framework modules are installed when framework is. +# ============================================================ +$(framework_module): | $(dir $(framework_module))framework-res.apk +$(framework_module): | $(dir $(framework_module))framework2.jar -framework_built := $(call java-lib-deps,framework) +framework_built := $(call java-lib-deps,framework framework2) -# AIDL files to be preprocessed and included in the SDK, -# relative to the root of the build tree. +# Copy AIDL files to be preprocessed and included in the SDK, +# specified relative to the root of the build tree. # ============================================================ +include $(CLEAR_VARS) + aidl_files := \ frameworks/base/core/java/android/accounts/IAccountManager.aidl \ frameworks/base/core/java/android/accounts/IAccountManagerResponse.aidl \ @@ -290,9 +353,11 @@ aidl_files := \ frameworks/base/core/java/android/content/Intent.aidl \ frameworks/base/core/java/android/content/IntentSender.aidl \ frameworks/base/core/java/android/content/PeriodicSync.aidl \ + frameworks/base/core/java/android/content/SyncRequest.aidl \ frameworks/base/core/java/android/content/SyncStats.aidl \ frameworks/base/core/java/android/content/res/Configuration.aidl \ frameworks/base/core/java/android/database/CursorWindow.aidl \ + frameworks/base/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl \ frameworks/base/core/java/android/net/Uri.aidl \ frameworks/base/core/java/android/nfc/NdefMessage.aidl \ frameworks/base/core/java/android/nfc/NdefRecord.aidl \ @@ -323,11 +388,14 @@ aidl_files := \ frameworks/base/location/java/android/location/Geofence.aidl \ frameworks/base/location/java/android/location/Location.aidl \ frameworks/base/location/java/android/location/LocationRequest.aidl \ + frameworks/base/location/java/android/location/FusedBatchOptions.aidl \ frameworks/base/location/java/com/android/internal/location/ProviderProperties.aidl \ frameworks/base/location/java/com/android/internal/location/ProviderRequest.aidl \ frameworks/base/telephony/java/android/telephony/ServiceState.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl \ + frameworks/base/wifi/java/android/net/wifi/BatchedScanSettings.aidl \ + frameworks/base/wifi/java/android/net/wifi/BatchedScanResult.aidl \ gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl $(gen): PRIVATE_SRC_FILES := $(aidl_files) @@ -364,6 +432,7 @@ include external/junit/Common.mk non_base_dirs := \ ../../external/apache-http/src/org/apache/http \ + ../opt/telephony/src/java/android/provider \ ../opt/telephony/src/java/android/telephony \ ../opt/telephony/src/java/android/telephony/gsm \ ../opt/net/voip/src/java/android/net/rtp \ @@ -420,6 +489,7 @@ framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \ okhttp \ ext \ framework \ + framework2 \ mms-common \ telephony-common \ voip-common @@ -452,11 +522,12 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS := \ -since $(SRC_API_DIR)/16.txt 16 \ -since $(SRC_API_DIR)/17.txt 17 \ -since $(SRC_API_DIR)/18.txt 18 \ + -since $(SRC_API_DIR)/19.txt 19 \ -werror -hide 113 \ -overview $(LOCAL_PATH)/core/java/overview.html framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR:= \ - $(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON) + $(call intermediates-dir-for,JAVA_LIBRARIES,framework-base,,COMMON) framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:= \ $(framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR) \ @@ -467,109 +538,121 @@ framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES := \ frameworks/base/docs/knowntags.txt sample_dir := development/samples +new_sample_dir := developers/samples/android + +# Whitelist of valid groups, used for default TOC grouping. Each sample must +# belong to one (and only one) group. Assign samples to groups by setting +# a sample.group var to one of these groups in the sample's _index.jd. +sample_groups := -samplegroup Input \ + -samplegroup Sensors \ + -samplegroup Connectivity # the list here should match the list of samples included in the sdk samples package # (see development/build/sdk.atree) # remove htmlified samples for now -- samples are still available through the SDK -# web_docs_sample_code_flags := \ +web_docs_sample_code_flags := \ -hdf android.hasSamples 1 \ + -samplecode $(new_sample_dir)/input/gestures/BasicGestureDetect/BasicGestureDetect \ + samples/BasicGestureDetect/ "Basic Gestures" \ -samplecode $(sample_dir)/AccelerometerPlay \ - resources/samples/AccelerometerPlay "Accelerometer Play" \ + samples/AccelerometerPlay "Accelerometer Play" \ -samplecode $(sample_dir)/ActionBarCompat \ - resources/samples/ActionBarCompat "Action Bar Compatibility" \ - -samplecode $(sample_dir)/AndroidBeamDemo \ - resources/samples/AndroidBeamDemo "Android Beam Demo" \ - -samplecode $(sample_dir)/ApiDemos \ - resources/samples/ApiDemos "API Demos" \ - -samplecode $(sample_dir)/Support4Demos \ - resources/samples/Support4Demos "API 4+ Support Demos" \ - -samplecode $(sample_dir)/Support13Demos \ - resources/samples/Support13Demos "API 13+ Support Demos" \ - -samplecode $(sample_dir)/BackupRestore \ - resources/samples/BackupRestore "Backup and Restore" \ - -samplecode $(sample_dir)/BluetoothChat \ - resources/samples/BluetoothChat "Bluetooth Chat" \ - -samplecode $(sample_dir)/BluetoothHDP \ - resources/samples/BluetoothHDP "Bluetooth HDP Demo" \ - -samplecode $(sample_dir)/BusinessCard \ - resources/samples/BusinessCard "Business Card" \ - -samplecode $(sample_dir)/ContactManager \ - resources/samples/ContactManager "Contact Manager" \ - -samplecode $(sample_dir)/CubeLiveWallpaper \ - resources/samples/CubeLiveWallpaper "Cube Live Wallpaper" \ - -samplecode $(sample_dir)/Home \ - resources/samples/Home "Home" \ - -samplecode $(sample_dir)/HoneycombGallery \ - resources/samples/HoneycombGallery "Honeycomb Gallery" \ - -samplecode $(sample_dir)/JetBoy \ - resources/samples/JetBoy "JetBoy" \ - -samplecode $(sample_dir)/KeyChainDemo \ - resources/samples/KeyChainDemo "KeyChain Demo" \ - -samplecode $(sample_dir)/LunarLander \ - resources/samples/LunarLander "Lunar Lander" \ - -samplecode $(sample_dir)/training/ads-and-ux \ - resources/samples/training/ads-and-ux "Mobile Advertisement Integration" \ - -samplecode $(sample_dir)/MultiResolution \ - resources/samples/MultiResolution "Multiple Resolutions" \ - -samplecode $(sample_dir)/training/multiscreen/newsreader \ - resources/samples/newsreader "News Reader" \ - -samplecode $(sample_dir)/NotePad \ - resources/samples/NotePad "Note Pad" \ - -samplecode $(sample_dir)/SpellChecker/SampleSpellCheckerService \ - resources/samples/SpellChecker/SampleSpellCheckerService "Spell Checker Service" \ - -samplecode $(sample_dir)/SpellChecker/HelloSpellChecker \ - resources/samples/SpellChecker/HelloSpellChecker "Spell Checker Client" \ - -samplecode $(sample_dir)/SampleSyncAdapter \ - resources/samples/SampleSyncAdapter "Sample Sync Adapter" \ - -samplecode $(sample_dir)/RandomMusicPlayer \ - resources/samples/RandomMusicPlayer "Random Music Player" \ - -samplecode $(sample_dir)/RenderScript \ - resources/samples/RenderScript "RenderScript" \ - -samplecode $(sample_dir)/SearchableDictionary \ - resources/samples/SearchableDictionary "Searchable Dictionary v2" \ - -samplecode $(sample_dir)/SipDemo \ - resources/samples/SipDemo "SIP Demo" \ - -samplecode $(sample_dir)/Snake \ - resources/samples/Snake "Snake" \ - -samplecode $(sample_dir)/SoftKeyboard \ - resources/samples/SoftKeyboard "Soft Keyboard" \ - -samplecode $(sample_dir)/Spinner \ - resources/samples/Spinner "Spinner" \ - -samplecode $(sample_dir)/SpinnerTest \ - resources/samples/SpinnerTest "SpinnerTest" \ - -samplecode $(sample_dir)/StackWidget \ - resources/samples/StackWidget "StackView Widget" \ - -samplecode $(sample_dir)/TicTacToeLib \ - resources/samples/TicTacToeLib "TicTacToeLib" \ - -samplecode $(sample_dir)/TicTacToeMain \ - resources/samples/TicTacToeMain "TicTacToeMain" \ - -samplecode $(sample_dir)/ToyVpn \ - resources/samples/ToyVpn "Toy VPN Client" \ - -samplecode $(sample_dir)/USB \ - resources/samples/USB "USB" \ - -samplecode $(sample_dir)/WeatherListWidget \ - resources/samples/WeatherListWidget "Weather List Widget" \ - -samplecode $(sample_dir)/WiFiDirectDemo \ - resources/samples/WiFiDirectDemo "Wi-Fi Direct Demo" \ - -samplecode $(sample_dir)/Wiktionary \ - resources/samples/Wiktionary "Wiktionary" \ - -samplecode $(sample_dir)/WiktionarySimple \ - resources/samples/WiktionarySimple "Wiktionary (Simplified)" \ - -samplecode $(sample_dir)/VoiceRecognitionService \ - resources/samples/VoiceRecognitionService "Voice Recognition Service" \ - -samplecode $(sample_dir)/VoicemailProviderDemo \ - resources/samples/VoicemailProviderDemo "Voicemail Provider Demo" \ - -samplecode $(sample_dir)/XmlAdapters \ - resources/samples/XmlAdapters "XML Adapters" \ - -samplecode $(sample_dir)/TtsEngine \ - resources/samples/TtsEngine "Text To Speech Engine" \ - -samplecode $(sample_dir)/training/device-management-policy \ - resources/samples/training/device-management-policy "Device Management Policy" + samples/ActionBarCompat "Action Bar Compatibility" \ + -samplecode $(sample_dir)/BluetoothHDP \ + samples/BluetoothHDP "Bluetooth HDP Demo" \ + -samplecode $(sample_dir)/BluetoothLeGatt \ + samples/BluetoothLeGatt "Bluetooth HDP Demo" +# -samplecode $(sample_dir)/AndroidBeamDemo \ +# samples/AndroidBeamDemo "Android Beam Demo" \ +# -samplecode $(sample_dir)/ApiDemos \ +# samples/ApiDemos "API Demos" \ +# -samplecode $(sample_dir)/Support4Demos \ +# samples/Support4Demos "API 4+ Support Demos" \ +# -samplecode $(sample_dir)/Support13Demos \ +# samples/Support13Demos "API 13+ Support Demos" \ +# -samplecode $(sample_dir)/BackupRestore \ +# samples/BackupRestore "Backup and Restore" \ +# -samplecode $(sample_dir)/BluetoothChat \ +# samples/BluetoothChat "Bluetooth Chat" \ +# -samplecode $(sample_dir)/BusinessCard \ +# samples/BusinessCard "Business Card" \ +# -samplecode $(sample_dir)/ContactManager \ +# samples/ContactManager "Contact Manager" \ +# -samplecode $(sample_dir)/CubeLiveWallpaper \ +# samples/CubeLiveWallpaper "Cube Live Wallpaper" \ +# -samplecode $(sample_dir)/Home \ +# samples/Home "Home" \ +# -samplecode $(sample_dir)/HoneycombGallery \ +# samples/HoneycombGallery "Honeycomb Gallery" \ +# -samplecode $(sample_dir)/JetBoy \ +# samples/JetBoy "JetBoy" \ +# -samplecode $(sample_dir)/KeyChainDemo \ +# samples/KeyChainDemo "KeyChain Demo" \ +# -samplecode $(sample_dir)/LunarLander \ +# samples/LunarLander "Lunar Lander" \ +# -samplecode $(sample_dir)/training/ads-and-ux \ +# samples/training/ads-and-ux "Mobile Advertisement Integration" \ +# -samplecode $(sample_dir)/MultiResolution \ +# samples/MultiResolution "Multiple Resolutions" \ +# -samplecode $(sample_dir)/training/multiscreen/newsreader \ +# samples/newsreader "News Reader" \ +# -samplecode $(sample_dir)/NotePad \ +# samples/NotePad "Note Pad" \ +# -samplecode $(sample_dir)/SpellChecker/SampleSpellCheckerService \ +# samples/SpellChecker/SampleSpellCheckerService "Spell Checker Service" \ +# -samplecode $(sample_dir)/SpellChecker/HelloSpellChecker \ +# samples/SpellChecker/HelloSpellChecker "Spell Checker Client" \ +# -samplecode $(sample_dir)/SampleSyncAdapter \ +# samples/SampleSyncAdapter "Sample Sync Adapter" \ +# -samplecode $(sample_dir)/RandomMusicPlayer \ +# samples/RandomMusicPlayer "Random Music Player" \ +# -samplecode $(sample_dir)/RenderScript \ +# samples/RenderScript "RenderScript" \ +# -samplecode $(sample_dir)/SearchableDictionary \ +# samples/SearchableDictionary "Searchable Dictionary v2" \ +# -samplecode $(sample_dir)/SipDemo \ +# samples/SipDemo "SIP Demo" \ +# -samplecode $(sample_dir)/Snake \ +# samples/Snake "Snake" \ +# -samplecode $(sample_dir)/SoftKeyboard \ +# samples/SoftKeyboard "Soft Keyboard" \ +# -samplecode $(sample_dir)/Spinner \ +# samples/Spinner "Spinner" \ +# -samplecode $(sample_dir)/SpinnerTest \ +# samples/SpinnerTest "SpinnerTest" \ +# -samplecode $(sample_dir)/StackWidget \ +# samples/StackWidget "StackView Widget" \ +# -samplecode $(sample_dir)/TicTacToeLib \ +# samples/TicTacToeLib "TicTacToeLib" \ +# -samplecode $(sample_dir)/TicTacToeMain \ +# samples/TicTacToeMain "TicTacToeMain" \ +# -samplecode $(sample_dir)/ToyVpn \ +# samples/ToyVpn "Toy VPN Client" \ +# -samplecode $(sample_dir)/USB \ +# samples/USB "USB" \ +# -samplecode $(sample_dir)/WeatherListWidget \ +# samples/WeatherListWidget "Weather List Widget" \ +# -samplecode $(sample_dir)/WiFiDirectDemo \ +# samples/WiFiDirectDemo "Wi-Fi Direct Demo" \ +# -samplecode $(sample_dir)/Wiktionary \ +# samples/Wiktionary "Wiktionary" \ +# -samplecode $(sample_dir)/WiktionarySimple \ +# samples/WiktionarySimple "Wiktionary (Simplified)" \ +# -samplecode $(sample_dir)/VoiceRecognitionService \ +# samples/VoiceRecognitionService "Voice Recognition Service" \ +# -samplecode $(sample_dir)/VoicemailProviderDemo \ +# samples/VoicemailProviderDemo "Voicemail Provider Demo" \ +# -samplecode $(sample_dir)/XmlAdapters \ +# samples/XmlAdapters "XML Adapters" \ +# -samplecode $(sample_dir)/TtsEngine \ +# samples/TtsEngine "Text To Speech Engine" \ +# -samplecode $(sample_dir)/training/device-management-policy \ +# samples/training/device-management-policy "Device Management Policy" ## SDK version identifiers used in the published docs # major[.minor] version for current SDK. (full releases only) -framework_docs_SDK_VERSION:=4.3 +framework_docs_SDK_VERSION:=4.4 # release version (ie "Release x") (full releases only) framework_docs_SDK_REL_ID:=1 @@ -656,13 +739,13 @@ LOCAL_MODULE := offline-sdk LOCAL_DROIDDOC_OPTIONS:=\ $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \ - $(web_docs_sample_code_flags) \ - -offlinemode \ + -offlinemode \ -title "Android SDK" \ -proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \ -todo $(OUT_DOCS)/$(LOCAL_MODULE)-docs-todo.html \ -sdkvalues $(OUT_DOCS) \ -hdf android.whichdoc offline +# $(web_docs_sample_code_flags) LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk @@ -696,10 +779,10 @@ LOCAL_MODULE := online-sdk LOCAL_DROIDDOC_OPTIONS:= \ $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \ - $(web_docs_sample_code_flags) \ -toroot / \ - -hdf android.whichdoc online \ - -hdf template.showLanguageMenu true + -hdf android.whichdoc online +# $(sample_groups) \ +# $(web_docs_sample_code_flags) LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk @@ -723,11 +806,11 @@ LOCAL_MODULE := ds LOCAL_DROIDDOC_OPTIONS:= \ $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \ - $(web_docs_sample_code_flags) \ -devsite \ -toroot / \ -hdf android.whichdoc online \ -hdf devsite true +# $(web_docs_sample_code_flags) LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk @@ -738,7 +821,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES) LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES) -LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES) framework +LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES) LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS) LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH) LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR) diff --git a/CleanSpec.mk b/CleanSpec.mk index 0c3ccecf6a098c402c102b95ab19d676f420fea8..cfa8be9a13c72391d3919855f2e76e9044d8a814 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -80,12 +80,12 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/IClipboard.P) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/android/internal/telephony/ITelephonyRegistry.P) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates) -$(call add-clean-step, rm -rf out/target/common/docs/api-stubs*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/docs/api-stubs*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/trustedlogic) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/com/trustedlogic) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/trustedlogic) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/com/trustedlogic) -$(call add-clean-step, rm -rf $(OUT_DIR)/target/target/common/obj/APPS/Music2_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Music2_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc/INdefTag.java) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libstagefright_aacdec_intermediates) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libstagefright_mp3dec_intermediates) @@ -161,6 +161,30 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodCallback.*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/framework-res_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) +$(call add-clean-step, rm -rf $(HOST_OUT)/obj/STATIC_LIBRARIES/libandroidfw_intermediates/import_includes) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrinterDiscoveryObserver.*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/printservice/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/packages/services/Proxy/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrinterDiscoverySessionObserver.*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrinterDiscoverySessionClient.*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/os/IBattery*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/usr/idc/frameworks) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/usr/keylayout/frameworks) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/usr/keychars/frameworks) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/media/video/*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/effects/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/framework-res_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrintClient.*) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/api/current.txt b/api/current.txt index 6f0bbcecff8ea958a8dfa558f5bedd3d29a07617..ff48db0efdbe97b68e226ce947165fb1e3b0cbbf 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23,12 +23,14 @@ package android { field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"; field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD"; field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"; + field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE"; field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS"; field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE"; field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE"; field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH"; field public static final java.lang.String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN"; + field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; field public static final java.lang.String BRICK = "android.permission.BRICK"; field public static final java.lang.String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED"; field public static final java.lang.String BROADCAST_SMS = "android.permission.BROADCAST_SMS"; @@ -37,6 +39,9 @@ package android { field public static final java.lang.String CALL_PHONE = "android.permission.CALL_PHONE"; field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED"; field public static final java.lang.String CAMERA = "android.permission.CAMERA"; + field public static final java.lang.String CAPTURE_AUDIO_OUTPUT = "android.permission.CAPTURE_AUDIO_OUTPUT"; + field public static final java.lang.String CAPTURE_SECURE_VIDEO_OUTPUT = "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"; + field public static final java.lang.String CAPTURE_VIDEO_OUTPUT = "android.permission.CAPTURE_VIDEO_OUTPUT"; field public static final java.lang.String CHANGE_COMPONENT_ENABLED_STATE = "android.permission.CHANGE_COMPONENT_ENABLED_STATE"; field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION"; field public static final java.lang.String CHANGE_NETWORK_STATE = "android.permission.CHANGE_NETWORK_STATE"; @@ -64,13 +69,16 @@ package android { field public static final java.lang.String INJECT_EVENTS = "android.permission.INJECT_EVENTS"; field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES"; + field public static final java.lang.String INSTALL_SHORTCUT = "com.android.launcher.permission.INSTALL_SHORTCUT"; field public static final java.lang.String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW"; field public static final java.lang.String INTERNET = "android.permission.INTERNET"; field public static final java.lang.String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES"; field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final java.lang.String MANAGE_ACCOUNTS = "android.permission.MANAGE_ACCOUNTS"; field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS"; + field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS"; field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; + field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS"; field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE"; field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS"; @@ -121,6 +129,8 @@ package android { field public static final java.lang.String SUBSCRIBED_FEEDS_READ = "android.permission.SUBSCRIBED_FEEDS_READ"; field public static final java.lang.String SUBSCRIBED_FEEDS_WRITE = "android.permission.SUBSCRIBED_FEEDS_WRITE"; field public static final java.lang.String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW"; + field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR"; + field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT"; field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final java.lang.String USE_CREDENTIALS = "android.permission.USE_CREDENTIALS"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; @@ -219,6 +229,7 @@ package android { field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 field public static final int accessibilityFlags = 16843652; // 0x1010384 + field public static final int accessibilityLiveRegion = 16843758; // 0x10103ee field public static final int accountPreferences = 16843423; // 0x101029f field public static final int accountType = 16843407; // 0x101028f field public static final int action = 16842797; // 0x101002d @@ -253,8 +264,10 @@ package android { field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8 field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9 + field public static final int addPrintersActivity = 16843750; // 0x10103e6 field public static final int addStatesFromChildren = 16842992; // 0x10100f0 field public static final int adjustViewBounds = 16843038; // 0x101011e + field public static final int advancedPrintOptionsActivity = 16843761; // 0x10103f1 field public static final int alertDialogIcon = 16843605; // 0x1010355 field public static final int alertDialogStyle = 16842845; // 0x101005d field public static final int alertDialogTheme = 16843529; // 0x1010309 @@ -280,12 +293,14 @@ package android { field public static final deprecated int animationResolution = 16843546; // 0x101031a field public static final int antialias = 16843034; // 0x101011a field public static final int anyDensity = 16843372; // 0x101026c + field public static final int apduServiceBanner = 16843757; // 0x10103ed field public static final int apiKey = 16843281; // 0x1010211 field public static final int author = 16843444; // 0x10102b4 field public static final int authorities = 16842776; // 0x1010018 field public static final int autoAdvanceViewId = 16843535; // 0x101030f field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b field public static final int autoLink = 16842928; // 0x10100b0 + field public static final int autoMirrored = 16843754; // 0x10103ea field public static final int autoStart = 16843445; // 0x10102b5 field public static final deprecated int autoText = 16843114; // 0x101016a field public static final int autoUrlDetect = 16843404; // 0x101028c @@ -326,6 +341,7 @@ package android { field public static final int canRetrieveWindowContent = 16843653; // 0x1010385 field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final deprecated int capitalize = 16843113; // 0x1010169 + field public static final int category = 16843752; // 0x10103e8 field public static final int centerBright = 16842956; // 0x10100cc field public static final int centerColor = 16843275; // 0x101020b field public static final int centerDark = 16842952; // 0x10100c8 @@ -474,6 +490,7 @@ package android { field public static final int fadeScrollbars = 16843434; // 0x10102aa field public static final int fadingEdge = 16842975; // 0x10100df field public static final int fadingEdgeLength = 16842976; // 0x10100e0 + field public static final int fadingMode = 16843745; // 0x10103e1 field public static final int fastScrollAlwaysVisible = 16843573; // 0x1010335 field public static final int fastScrollEnabled = 16843302; // 0x1010226 field public static final int fastScrollOverlayPosition = 16843578; // 0x101033a @@ -513,6 +530,7 @@ package android { field public static final int freezesText = 16843116; // 0x101016c field public static final int fromAlpha = 16843210; // 0x10101ca field public static final int fromDegrees = 16843187; // 0x10101b3 + field public static final int fromScene = 16843741; // 0x10103dd field public static final int fromXDelta = 16843206; // 0x10101c6 field public static final int fromXScale = 16843202; // 0x10101c2 field public static final int fromYDelta = 16843208; // 0x10101c8 @@ -598,6 +616,7 @@ package android { field public static final int installLocation = 16843447; // 0x10102b7 field public static final int interpolator = 16843073; // 0x1010141 field public static final int isAlwaysSyncable = 16843571; // 0x1010333 + field public static final int isAsciiCapable = 16843753; // 0x10103e9 field public static final int isAuxiliary = 16843647; // 0x101037f field public static final int isDefault = 16843297; // 0x1010221 field public static final int isIndicator = 16843079; // 0x1010147 @@ -621,6 +640,7 @@ package android { field public static final int keyPreviewHeight = 16843321; // 0x1010239 field public static final int keyPreviewLayout = 16843319; // 0x1010237 field public static final int keyPreviewOffset = 16843320; // 0x1010238 + field public static final int keySet = 16843739; // 0x10103db field public static final int keyTextColor = 16843318; // 0x1010236 field public static final int keyTextSize = 16843316; // 0x1010234 field public static final int keyWidth = 16843325; // 0x101023d @@ -853,6 +873,7 @@ package android { field public static final int reqKeyboardType = 16843304; // 0x1010228 field public static final int reqNavigation = 16843306; // 0x101022a field public static final int reqTouchScreen = 16843303; // 0x1010227 + field public static final int requireDeviceUnlock = 16843756; // 0x10103ec field public static final int required = 16843406; // 0x101028e field public static final int requiredAccountType = 16843734; // 0x10103d6 field public static final int requiredForAllUsers = 16843728; // 0x10103d0 @@ -951,9 +972,13 @@ package android { field public static final int spinnersShown = 16843595; // 0x101034b field public static final int splitMotionEvents = 16843503; // 0x10102ef field public static final int src = 16843033; // 0x1010119 + field public static final int ssp = 16843747; // 0x10103e3 + field public static final int sspPattern = 16843749; // 0x10103e5 + field public static final int sspPrefix = 16843748; // 0x10103e4 field public static final int stackFromBottom = 16843005; // 0x10100fd field public static final int starStyle = 16842882; // 0x1010082 field public static final int startColor = 16843165; // 0x101019d + field public static final int startDelay = 16843746; // 0x10103e2 field public static final int startOffset = 16843198; // 0x10101be field public static final deprecated int startYear = 16843132; // 0x101017c field public static final int stateNotNeeded = 16842774; // 0x1010016 @@ -997,6 +1022,7 @@ package android { field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsRtl = 16843695; // 0x10103af + field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb field public static final int supportsUploading = 16843419; // 0x101029b field public static final int switchMinWidth = 16843632; // 0x1010370 field public static final int switchPadding = 16843633; // 0x1010371 @@ -1013,6 +1039,7 @@ package android { field public static final int targetActivity = 16843266; // 0x1010202 field public static final int targetClass = 16842799; // 0x101002f field public static final int targetDescriptions = 16843680; // 0x10103a0 + field public static final int targetId = 16843740; // 0x10103dc field public static final int targetPackage = 16842785; // 0x1010021 field public static final int targetSdkVersion = 16843376; // 0x1010270 field public static final int taskAffinity = 16842770; // 0x1010012 @@ -1101,6 +1128,7 @@ package android { field public static final int titleTextStyle = 16843512; // 0x10102f8 field public static final int toAlpha = 16843211; // 0x10101cb field public static final int toDegrees = 16843188; // 0x10101b4 + field public static final int toScene = 16843742; // 0x10103de field public static final int toXDelta = 16843207; // 0x10101c7 field public static final int toXScale = 16843203; // 0x10101c3 field public static final int toYDelta = 16843209; // 0x10101c9 @@ -1115,6 +1143,8 @@ package android { field public static final int transcriptMode = 16843008; // 0x1010100 field public static final int transformPivotX = 16843552; // 0x1010320 field public static final int transformPivotY = 16843553; // 0x1010321 + field public static final int transition = 16843743; // 0x10103df + field public static final int transitionOrdering = 16843744; // 0x10103e0 field public static final int translationX = 16843554; // 0x1010322 field public static final int translationY = 16843555; // 0x1010323 field public static final int type = 16843169; // 0x10101a1 @@ -1133,6 +1163,7 @@ package android { field public static final int valueTo = 16843487; // 0x10102df field public static final int valueType = 16843488; // 0x10102e0 field public static final int variablePadding = 16843157; // 0x1010195 + field public static final int vendor = 16843751; // 0x10103e7 field public static final int versionCode = 16843291; // 0x101021b field public static final int versionName = 16843292; // 0x101021c field public static final int verticalCorrection = 16843322; // 0x101023a @@ -1192,6 +1223,8 @@ package android { field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c field public static final int windowTitleSize = 16842842; // 0x101005a field public static final int windowTitleStyle = 16842843; // 0x101005b + field public static final int windowTranslucentNavigation = 16843760; // 0x10103f0 + field public static final int windowTranslucentStatus = 16843759; // 0x10103ef field public static final int writePermission = 16842760; // 0x1010008 field public static final int x = 16842924; // 0x10100ac field public static final int xlargeScreens = 16843455; // 0x10102bf @@ -1724,10 +1757,12 @@ package android { field public static final int Theme_DeviceDefault_Light_NoActionBar = 16974124; // 0x103012c field public static final int Theme_DeviceDefault_Light_NoActionBar_Fullscreen = 16974125; // 0x103012d field public static final int Theme_DeviceDefault_Light_NoActionBar_Overscan = 16974304; // 0x10301e0 + field public static final int Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor = 16974308; // 0x10301e4 field public static final int Theme_DeviceDefault_Light_Panel = 16974139; // 0x103013b field public static final int Theme_DeviceDefault_NoActionBar = 16974121; // 0x1030129 field public static final int Theme_DeviceDefault_NoActionBar_Fullscreen = 16974122; // 0x103012a field public static final int Theme_DeviceDefault_NoActionBar_Overscan = 16974303; // 0x10301df + field public static final int Theme_DeviceDefault_NoActionBar_TranslucentDecor = 16974307; // 0x10301e3 field public static final int Theme_DeviceDefault_Panel = 16974138; // 0x103013a field public static final int Theme_DeviceDefault_Wallpaper = 16974140; // 0x103013c field public static final int Theme_DeviceDefault_Wallpaper_NoTitleBar = 16974141; // 0x103013d @@ -1751,10 +1786,12 @@ package android { field public static final int Theme_Holo_Light_NoActionBar = 16974064; // 0x10300f0 field public static final int Theme_Holo_Light_NoActionBar_Fullscreen = 16974065; // 0x10300f1 field public static final int Theme_Holo_Light_NoActionBar_Overscan = 16974302; // 0x10301de + field public static final int Theme_Holo_Light_NoActionBar_TranslucentDecor = 16974306; // 0x10301e2 field public static final int Theme_Holo_Light_Panel = 16973948; // 0x103007c field public static final int Theme_Holo_NoActionBar = 16973932; // 0x103006c field public static final int Theme_Holo_NoActionBar_Fullscreen = 16973933; // 0x103006d field public static final int Theme_Holo_NoActionBar_Overscan = 16974301; // 0x10301dd + field public static final int Theme_Holo_NoActionBar_TranslucentDecor = 16974305; // 0x10301e1 field public static final int Theme_Holo_Panel = 16973947; // 0x103007b field public static final int Theme_Holo_Wallpaper = 16973949; // 0x103007d field public static final int Theme_Holo_Wallpaper_NoTitleBar = 16973950; // 0x103007e @@ -2316,6 +2353,7 @@ package android.animation { public abstract class Animator implements java.lang.Cloneable { ctor public Animator(); method public void addListener(android.animation.Animator.AnimatorListener); + method public void addPauseListener(android.animation.Animator.AnimatorPauseListener); method public void cancel(); method public android.animation.Animator clone(); method public void end(); @@ -2323,10 +2361,14 @@ package android.animation { method public android.animation.TimeInterpolator getInterpolator(); method public java.util.ArrayList getListeners(); method public abstract long getStartDelay(); + method public boolean isPaused(); method public abstract boolean isRunning(); method public boolean isStarted(); + method public void pause(); method public void removeAllListeners(); method public void removeListener(android.animation.Animator.AnimatorListener); + method public void removePauseListener(android.animation.Animator.AnimatorPauseListener); + method public void resume(); method public abstract android.animation.Animator setDuration(long); method public abstract void setInterpolator(android.animation.TimeInterpolator); method public abstract void setStartDelay(long); @@ -2343,16 +2385,23 @@ package android.animation { method public abstract void onAnimationStart(android.animation.Animator); } + public static abstract interface Animator.AnimatorPauseListener { + method public abstract void onAnimationPause(android.animation.Animator); + method public abstract void onAnimationResume(android.animation.Animator); + } + public class AnimatorInflater { ctor public AnimatorInflater(); method public static android.animation.Animator loadAnimator(android.content.Context, int) throws android.content.res.Resources.NotFoundException; } - public abstract class AnimatorListenerAdapter implements android.animation.Animator.AnimatorListener { + public abstract class AnimatorListenerAdapter implements android.animation.Animator.AnimatorListener android.animation.Animator.AnimatorPauseListener { ctor public AnimatorListenerAdapter(); method public void onAnimationCancel(android.animation.Animator); method public void onAnimationEnd(android.animation.Animator); + method public void onAnimationPause(android.animation.Animator); method public void onAnimationRepeat(android.animation.Animator); + method public void onAnimationResume(android.animation.Animator); method public void onAnimationStart(android.animation.Animator); } @@ -2808,6 +2857,7 @@ package android.app { method public void recreate(); method public void registerForContextMenu(android.view.View); method public final deprecated void removeDialog(int); + method public void reportFullyDrawn(); method public final boolean requestWindowFeature(int); method public final void runOnUiThread(java.lang.Runnable); method public void setContentView(int); @@ -2877,6 +2927,8 @@ package android.app { } public class ActivityManager { + method public boolean clearApplicationUserData(); + method public void dumpPackageState(java.io.FileDescriptor, java.lang.String); method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo(); method public int getLargeMemoryClass(); method public int getLauncherLargeIconDensity(); @@ -2891,12 +2943,14 @@ package android.app { method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException; method public java.util.List getRunningServices(int) throws java.lang.SecurityException; method public java.util.List getRunningTasks(int) throws java.lang.SecurityException; + method public boolean isLowRamDevice(); method public static boolean isRunningInTestHarness(); method public static boolean isUserAMonkey(); method public void killBackgroundProcesses(java.lang.String); method public void moveTaskToFront(int, int); method public void moveTaskToFront(int, int, android.os.Bundle); method public deprecated void restartPackage(java.lang.String); + field public static final java.lang.String META_HOME_ALTERNATE = "android.app.home.alternate"; field public static final int MOVE_TASK_NO_USER_ACTION = 2; // 0x2 field public static final int MOVE_TASK_WITH_HOME = 1; // 0x1 field public static final int RECENT_IGNORE_UNAVAILABLE = 2; // 0x2 @@ -3028,17 +3082,19 @@ package android.app { public class AlarmManager { method public void cancel(android.app.PendingIntent); method public void set(int, long, android.app.PendingIntent); - method public void setInexactRepeating(int, long, long, android.app.PendingIntent); + method public void setExact(int, long, android.app.PendingIntent); + method public deprecated void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method public void setTime(long); method public void setTimeZone(java.lang.String); + method public void setWindow(int, long, long, android.app.PendingIntent); field public static final int ELAPSED_REALTIME = 3; // 0x3 field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2 - field public static final long INTERVAL_DAY = 86400000L; // 0x5265c00L - field public static final long INTERVAL_FIFTEEN_MINUTES = 900000L; // 0xdbba0L - field public static final long INTERVAL_HALF_DAY = 43200000L; // 0x2932e00L - field public static final long INTERVAL_HALF_HOUR = 1800000L; // 0x1b7740L - field public static final long INTERVAL_HOUR = 3600000L; // 0x36ee80L + field public static final deprecated long INTERVAL_DAY = 86400000L; // 0x5265c00L + field public static final deprecated long INTERVAL_FIFTEEN_MINUTES = 900000L; // 0xdbba0L + field public static final deprecated long INTERVAL_HALF_DAY = 43200000L; // 0x2932e00L + field public static final deprecated long INTERVAL_HALF_HOUR = 1800000L; // 0x1b7740L + field public static final deprecated long INTERVAL_HOUR = 3600000L; // 0x36ee80L field public static final int RTC = 1; // 0x1 field public static final int RTC_WAKEUP = 0; // 0x0 } @@ -3116,6 +3172,30 @@ package android.app { ctor public AliasActivity(); } + public class AppOpsManager { + method public int checkOp(java.lang.String, int, java.lang.String); + method public int checkOpNoThrow(java.lang.String, int, java.lang.String); + method public void checkPackage(int, java.lang.String); + method public void finishOp(java.lang.String, int, java.lang.String); + method public int noteOp(java.lang.String, int, java.lang.String); + method public int noteOpNoThrow(java.lang.String, int, java.lang.String); + method public int startOp(java.lang.String, int, java.lang.String); + method public int startOpNoThrow(java.lang.String, int, java.lang.String); + method public void startWatchingMode(java.lang.String, java.lang.String, android.app.AppOpsManager.OnOpChangedListener); + method public void stopWatchingMode(android.app.AppOpsManager.OnOpChangedListener); + field public static final int MODE_ALLOWED = 0; // 0x0 + field public static final int MODE_ERRORED = 2; // 0x2 + field public static final int MODE_IGNORED = 1; // 0x1 + field public static final java.lang.String OPSTR_COARSE_LOCATION = "android:coarse_location"; + field public static final java.lang.String OPSTR_FINE_LOCATION = "android:fine_location"; + field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power"; + field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location"; + } + + public static abstract interface AppOpsManager.OnOpChangedListener { + method public abstract void onOpChanged(java.lang.String, java.lang.String); + } + public class Application extends android.content.ContextWrapper implements android.content.ComponentCallbacks2 { ctor public Application(); method public void onConfigurationChanged(android.content.res.Configuration); @@ -3839,6 +3919,23 @@ package android.app { field public static final int DEFAULT_LIGHTS = 4; // 0x4 field public static final int DEFAULT_SOUND = 1; // 0x1 field public static final int DEFAULT_VIBRATE = 2; // 0x2 + field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText"; + field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon"; + field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big"; + field public static final java.lang.String EXTRA_PEOPLE = "android.people"; + field public static final java.lang.String EXTRA_PICTURE = "android.picture"; + field public static final java.lang.String EXTRA_PROGRESS = "android.progress"; + field public static final java.lang.String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; + field public static final java.lang.String EXTRA_PROGRESS_MAX = "android.progressMax"; + field public static final java.lang.String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; + field public static final java.lang.String EXTRA_SHOW_WHEN = "android.showWhen"; + field public static final java.lang.String EXTRA_SMALL_ICON = "android.icon"; + field public static final java.lang.String EXTRA_SUB_TEXT = "android.subText"; + field public static final java.lang.String EXTRA_SUMMARY_TEXT = "android.summaryText"; + field public static final java.lang.String EXTRA_TEXT = "android.text"; + field public static final java.lang.String EXTRA_TEXT_LINES = "android.textLines"; + field public static final java.lang.String EXTRA_TITLE = "android.title"; + field public static final java.lang.String EXTRA_TITLE_BIG = "android.title.big"; field public static final int FLAG_AUTO_CANCEL = 16; // 0x10 field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40 field public static final deprecated int FLAG_HIGH_PRIORITY = 128; // 0x80 @@ -3853,12 +3950,14 @@ package android.app { field public static final int PRIORITY_MAX = 2; // 0x2 field public static final int PRIORITY_MIN = -2; // 0xfffffffe field public static final int STREAM_DEFAULT = -1; // 0xffffffff + field public android.app.Notification.Action[] actions; field public int audioStreamType; field public android.widget.RemoteViews bigContentView; field public android.app.PendingIntent contentIntent; field public android.widget.RemoteViews contentView; field public int defaults; field public android.app.PendingIntent deleteIntent; + field public android.os.Bundle extras; field public int flags; field public android.app.PendingIntent fullScreenIntent; field public int icon; @@ -3876,12 +3975,22 @@ package android.app { field public long when; } + public static class Notification.Action implements android.os.Parcelable { + ctor public Notification.Action(int, java.lang.CharSequence, android.app.PendingIntent); + method public android.app.Notification.Action clone(); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public android.app.PendingIntent actionIntent; + field public int icon; + field public java.lang.CharSequence title; + } + public static class Notification.BigPictureStyle extends android.app.Notification.Style { ctor public Notification.BigPictureStyle(); ctor public Notification.BigPictureStyle(android.app.Notification.Builder); method public android.app.Notification.BigPictureStyle bigLargeIcon(android.graphics.Bitmap); method public android.app.Notification.BigPictureStyle bigPicture(android.graphics.Bitmap); - method public android.app.Notification build(); method public android.app.Notification.BigPictureStyle setBigContentTitle(java.lang.CharSequence); method public android.app.Notification.BigPictureStyle setSummaryText(java.lang.CharSequence); } @@ -3890,7 +3999,6 @@ package android.app { ctor public Notification.BigTextStyle(); ctor public Notification.BigTextStyle(android.app.Notification.Builder); method public android.app.Notification.BigTextStyle bigText(java.lang.CharSequence); - method public android.app.Notification build(); method public android.app.Notification.BigTextStyle setBigContentTitle(java.lang.CharSequence); method public android.app.Notification.BigTextStyle setSummaryText(java.lang.CharSequence); } @@ -3908,6 +4016,7 @@ package android.app { method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence); method public android.app.Notification.Builder setDefaults(int); method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent); + method public android.app.Notification.Builder setExtras(android.os.Bundle); method public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean); method public android.app.Notification.Builder setLargeIcon(android.graphics.Bitmap); method public android.app.Notification.Builder setLights(int, int, int); @@ -3934,14 +4043,13 @@ package android.app { ctor public Notification.InboxStyle(); ctor public Notification.InboxStyle(android.app.Notification.Builder); method public android.app.Notification.InboxStyle addLine(java.lang.CharSequence); - method public android.app.Notification build(); method public android.app.Notification.InboxStyle setBigContentTitle(java.lang.CharSequence); method public android.app.Notification.InboxStyle setSummaryText(java.lang.CharSequence); } public static abstract class Notification.Style { ctor public Notification.Style(); - method public abstract android.app.Notification build(); + method public android.app.Notification build(); method protected void checkBuilder(); method protected android.widget.RemoteViews getStandardView(int); method protected void internalSetBigContentTitle(java.lang.CharSequence); @@ -4257,6 +4365,9 @@ package android.app { method public void clear() throws java.io.IOException; method public void clearWallpaperOffsets(android.os.IBinder); method public void forgetLoadedWallpaper(); + method public android.graphics.drawable.Drawable getBuiltInDrawable(); + method public android.graphics.drawable.Drawable getBuiltInDrawable(int, int, boolean, float, float); + method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri); method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); method public android.graphics.drawable.Drawable getDrawable(); @@ -4274,6 +4385,7 @@ package android.app { method public void setWallpaperOffsets(android.os.IBinder, float, float); method public void suggestDesiredDimensions(int, int); field public static final java.lang.String ACTION_CHANGE_LIVE_WALLPAPER = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER"; + field public static final java.lang.String ACTION_CROP_AND_SET_WALLPAPER = "android.service.wallpaper.CROP_AND_SET_WALLPAPER"; field public static final java.lang.String ACTION_LIVE_WALLPAPER_CHOOSER = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; field public static final java.lang.String COMMAND_DROP = "android.home.drop"; field public static final java.lang.String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap"; @@ -4675,68 +4787,165 @@ package android.bluetooth { } public class BluetoothAssignedNumbers { + field public static final int AAMP_OF_AMERICA = 190; // 0xbe field public static final int ACCEL_SEMICONDUCTOR = 74; // 0x4a + field public static final int ACE_SENSOR = 188; // 0xbc + field public static final int ADIDAS = 195; // 0xc3 + field public static final int ADVANCED_PANMOBIL_SYSTEMS = 145; // 0x91 + field public static final int AIROHA_TECHNOLOGY = 148; // 0x94 field public static final int ALCATEL = 36; // 0x24 + field public static final int ALPWISE = 154; // 0x9a + field public static final int AMICCOM_ELECTRONICS = 192; // 0xc0 + field public static final int APLIX = 189; // 0xbd field public static final int APPLE = 76; // 0x4c field public static final int APT_LICENSING = 79; // 0x4f + field public static final int ARCHOS = 207; // 0xcf + field public static final int ARP_DEVICES = 168; // 0xa8 field public static final int ATHEROS_COMMUNICATIONS = 69; // 0x45 field public static final int ATMEL = 19; // 0x13 + field public static final int AUSTCO_COMMUNICATION_SYSTEMS = 213; // 0xd5 + field public static final int AUTONET_MOBILE = 127; // 0x7f field public static final int AVAGO = 78; // 0x4e field public static final int AVM_BERLIN = 31; // 0x1f + field public static final int A_AND_D_ENGINEERING = 105; // 0x69 + field public static final int A_AND_R_CAMBRIDGE = 124; // 0x7c field public static final int BANDSPEED = 32; // 0x20 + field public static final int BAND_XI_INTERNATIONAL = 100; // 0x64 + field public static final int BDE_TECHNOLOGY = 180; // 0xb4 + field public static final int BEATS_ELECTRONICS = 204; // 0xcc + field public static final int BEAUTIFUL_ENTERPRISE = 108; // 0x6c + field public static final int BEKEY = 178; // 0xb2 field public static final int BELKIN_INTERNATIONAL = 92; // 0x5c + field public static final int BINAURIC = 203; // 0xcb + field public static final int BIOSENTRONICS = 219; // 0xdb field public static final int BLUEGIGA = 71; // 0x47 + field public static final int BLUERADIOS = 133; // 0x85 field public static final int BLUETOOTH_SIG = 63; // 0x3f + field public static final int BLUETREK_TECHNOLOGIES = 151; // 0x97 + field public static final int BOSE = 158; // 0x9e + field public static final int BRIARTEK = 109; // 0x6d field public static final int BROADCOM = 15; // 0xf + field public static final int CAEN_RFID = 170; // 0xaa field public static final int CAMBRIDGE_SILICON_RADIO = 10; // 0xa field public static final int CATC = 52; // 0x34 + field public static final int CINETIX = 175; // 0xaf + field public static final int CLARINOX_TECHNOLOGIES = 179; // 0xb3 + field public static final int COLORFY = 156; // 0x9c field public static final int COMMIL = 51; // 0x33 field public static final int CONEXANT_SYSTEMS = 28; // 0x1c + field public static final int CONNECTBLUE = 113; // 0x71 field public static final int CONTINENTAL_AUTOMOTIVE = 75; // 0x4b field public static final int CONWISE_TECHNOLOGY = 66; // 0x42 + field public static final int CREATIVE_TECHNOLOGY = 118; // 0x76 field public static final int C_TECHNOLOGIES = 38; // 0x26 + field public static final int DANLERS = 225; // 0xe1 + field public static final int DELORME_PUBLISHING_COMPANY = 128; // 0x80 + field public static final int DEXCOM = 208; // 0xd0 + field public static final int DIALOG_SEMICONDUCTOR = 210; // 0xd2 field public static final int DIGIANSWER = 12; // 0xc field public static final int ECLIPSE = 53; // 0x35 + field public static final int ECOTEST = 136; // 0x88 + field public static final int ELGATO_SYSTEMS = 206; // 0xce field public static final int EM_MICROELECTRONIC_MARIN = 90; // 0x5a + field public static final int EQUINOX_AG = 134; // 0x86 field public static final int ERICSSON_TECHNOLOGY = 0; // 0x0 + field public static final int EVLUMA = 201; // 0xc9 field public static final int FREE2MOVE = 83; // 0x53 + field public static final int FUNAI_ELECTRIC = 144; // 0x90 + field public static final int GARMIN_INTERNATIONAL = 135; // 0x87 field public static final int GCT_SEMICONDUCTOR = 45; // 0x2d + field public static final int GELO = 200; // 0xc8 + field public static final int GENEQ = 194; // 0xc2 + field public static final int GENERAL_MOTORS = 104; // 0x68 field public static final int GENNUM = 59; // 0x3b + field public static final int GEOFORCE = 157; // 0x9d + field public static final int GIBSON_GUITARS = 98; // 0x62 + field public static final int GN_NETCOM = 103; // 0x67 + field public static final int GN_RESOUND = 137; // 0x89 + field public static final int GOOGLE = 224; // 0xe0 + field public static final int GREEN_THROTTLE_GAMES = 172; // 0xac + field public static final int GROUP_SENSE = 115; // 0x73 + field public static final int HANLYNN_TECHNOLOGIES = 123; // 0x7b field public static final int HARMAN_INTERNATIONAL = 87; // 0x57 + field public static final int HEWLETT_PACKARD = 101; // 0x65 field public static final int HITACHI = 41; // 0x29 + field public static final int HOSIDEN = 221; // 0xdd field public static final int IBM = 3; // 0x3 field public static final int INFINEON_TECHNOLOGIES = 9; // 0x9 + field public static final int INGENIEUR_SYSTEMGRUPPE_ZAHN = 171; // 0xab field public static final int INTEGRATED_SILICON_SOLUTION = 65; // 0x41 field public static final int INTEGRATED_SYSTEM_SOLUTION = 57; // 0x39 field public static final int INTEL = 2; // 0x2 field public static final int INVENTEL = 30; // 0x1e field public static final int IPEXTREME = 61; // 0x3d + field public static final int I_TECH_DYNAMIC_GLOBAL_DISTRIBUTION = 153; // 0x99 + field public static final int JAWBONE = 138; // 0x8a + field public static final int JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS = 155; // 0x9b + field public static final int JOHNSON_CONTROLS = 185; // 0xb9 field public static final int J_AND_M = 82; // 0x52 + field public static final int KAWANTECH = 212; // 0xd4 field public static final int KC_TECHNOLOGY = 22; // 0x16 + field public static final int KENSINGTON_COMPUTER_PRODUCTS_GROUP = 160; // 0xa0 + field public static final int LAIRD_TECHNOLOGIES = 119; // 0x77 + field public static final int LESSWIRE = 121; // 0x79 + field public static final int LG_ELECTRONICS = 196; // 0xc4 + field public static final int LINAK = 164; // 0xa4 field public static final int LUCENT = 7; // 0x7 + field public static final int LUDUS_HELSINKI = 132; // 0x84 field public static final int MACRONIX = 44; // 0x2c + field public static final int MAGNETI_MARELLI = 169; // 0xa9 field public static final int MANSELLA = 33; // 0x21 field public static final int MARVELL = 72; // 0x48 field public static final int MATSUSHITA_ELECTRIC = 58; // 0x3a + field public static final int MC10 = 202; // 0xca field public static final int MEDIATEK = 70; // 0x46 + field public static final int MESO_INTERNATIONAL = 182; // 0xb6 + field public static final int META_WATCH = 163; // 0xa3 field public static final int MEWTEL_TECHNOLOGY = 47; // 0x2f + field public static final int MICOMMAND = 99; // 0x63 + field public static final int MICROCHIP_TECHNOLOGY = 205; // 0xcd field public static final int MICROSOFT = 6; // 0x6 + field public static final int MINDTREE = 106; // 0x6a + field public static final int MISFIT_WEARABLES = 223; // 0xdf field public static final int MITEL_SEMICONDUCTOR = 16; // 0x10 field public static final int MITSUBISHI_ELECTRIC = 20; // 0x14 field public static final int MOBILIAN_CORPORATION = 55; // 0x37 + field public static final int MONSTER = 112; // 0x70 field public static final int MOTOROLA = 8; // 0x8 + field public static final int MSTAR_SEMICONDUCTOR = 122; // 0x7a + field public static final int MUZIK = 222; // 0xde field public static final int NEC = 34; // 0x22 + field public static final int NEC_LIGHTING = 149; // 0x95 field public static final int NEWLOGIC = 23; // 0x17 + field public static final int NIKE = 120; // 0x78 + field public static final int NINE_SOLUTIONS = 102; // 0x66 field public static final int NOKIA_MOBILE_PHONES = 1; // 0x1 field public static final int NORDIC_SEMICONDUCTOR = 89; // 0x59 field public static final int NORWOOD_SYSTEMS = 46; // 0x2e + field public static final int ODM_TECHNOLOGY = 150; // 0x96 + field public static final int OMEGAWAVE = 174; // 0xae + field public static final int ONSET_COMPUTER = 197; // 0xc5 field public static final int OPEN_INTERFACE = 39; // 0x27 + field public static final int OTL_DYNAMICS = 165; // 0xa5 + field public static final int PANDA_OCEAN = 166; // 0xa6 field public static final int PARROT = 67; // 0x43 field public static final int PARTHUS_TECHNOLOGIES = 14; // 0xe + field public static final int PASSIF_SEMICONDUCTOR = 176; // 0xb0 + field public static final int PETER_SYSTEMTECHNIK = 173; // 0xad field public static final int PHILIPS_SEMICONDUCTORS = 37; // 0x25 field public static final int PLANTRONICS = 85; // 0x55 + field public static final int POLAR_ELECTRO = 107; // 0x6b + field public static final int POLAR_ELECTRO_EUROPE = 209; // 0xd1 + field public static final int PROCTER_AND_GAMBLE = 220; // 0xdc field public static final int QUALCOMM = 29; // 0x1d + field public static final int QUALCOMM_CONNECTED_EXPERIENCES = 216; // 0xd8 + field public static final int QUALCOMM_INNOVATION_CENTER = 184; // 0xb8 + field public static final int QUALCOMM_LABS = 140; // 0x8c + field public static final int QUALCOMM_TECHNOLOGIES = 215; // 0xd7 + field public static final int QUINTIC = 142; // 0x8e + field public static final int QUUPPA = 199; // 0xc7 field public static final int RALINK_TECHNOLOGY = 91; // 0x5b + field public static final int RDA_MICROELECTRONICS = 97; // 0x61 field public static final int REALTEK_SEMICONDUCTOR = 93; // 0x5d field public static final int RED_M = 50; // 0x32 field public static final int RENESAS_TECHNOLOGY = 54; // 0x36 @@ -4745,33 +4954,66 @@ package android.bluetooth { field public static final int RIVIERAWAVES = 96; // 0x60 field public static final int ROHDE_AND_SCHWARZ = 25; // 0x19 field public static final int RTX_TELECOM = 21; // 0x15 + field public static final int SAMSUNG_ELECTRONICS = 117; // 0x75 + field public static final int SARIS_CYCLING_GROUP = 177; // 0xb1 + field public static final int SEERS_TECHNOLOGY = 125; // 0x7d field public static final int SEIKO_EPSON = 64; // 0x40 + field public static final int SELFLY = 198; // 0xc6 + field public static final int SEMILINK = 226; // 0xe2 + field public static final int SENNHEISER_COMMUNICATIONS = 130; // 0x82 + field public static final int SHANGHAI_SUPER_SMART_ELECTRONICS = 114; // 0x72 + field public static final int SHENZHEN_EXCELSECU_DATA_TECHNOLOGY = 193; // 0xc1 field public static final int SIGNIA_TECHNOLOGIES = 27; // 0x1b field public static final int SILICON_WAVE = 11; // 0xb field public static final int SIRF_TECHNOLOGY = 80; // 0x50 field public static final int SOCKET_MOBILE = 68; // 0x44 field public static final int SONY_ERICSSON = 86; // 0x56 + field public static final int SOUND_ID = 111; // 0x6f + field public static final int SPORTS_TRACKING_TECHNOLOGIES = 126; // 0x7e + field public static final int SR_MEDIZINELEKTRONIK = 161; // 0xa1 field public static final int STACCATO_COMMUNICATIONS = 77; // 0x4d + field public static final int STALMART_TECHNOLOGY = 191; // 0xbf + field public static final int STARKEY_LABORATORIES = 186; // 0xba + field public static final int STOLLMAN_E_PLUS_V = 143; // 0x8f field public static final int STONESTREET_ONE = 94; // 0x5e field public static final int ST_MICROELECTRONICS = 48; // 0x30 + field public static final int SUMMIT_DATA_COMMUNICATIONS = 110; // 0x6e + field public static final int SUUNTO = 159; // 0x9f + field public static final int SWIRL_NETWORKS = 181; // 0xb5 field public static final int SYMBOL_TECHNOLOGIES = 42; // 0x2a field public static final int SYNOPSYS = 49; // 0x31 field public static final int SYSTEMS_AND_CHIPS = 62; // 0x3e + field public static final int S_POWER_ELECTRONICS = 187; // 0xbb + field public static final int TAIXINGBANG_TECHNOLOGY = 211; // 0xd3 field public static final int TENOVIS = 43; // 0x2b field public static final int TERAX = 56; // 0x38 field public static final int TEXAS_INSTRUMENTS = 13; // 0xd + field public static final int THINKOPTICS = 146; // 0x92 field public static final int THREECOM = 5; // 0x5 field public static final int THREE_DIJOY = 84; // 0x54 field public static final int THREE_DSP = 73; // 0x49 + field public static final int TIMEKEEPING_SYSTEMS = 131; // 0x83 + field public static final int TIMEX_GROUP_USA = 214; // 0xd6 + field public static final int TOPCORN_POSITIONING_SYSTEMS = 139; // 0x8b field public static final int TOSHIBA = 4; // 0x4 field public static final int TRANSILICA = 24; // 0x18 + field public static final int TRELAB = 183; // 0xb7 field public static final int TTPCOM = 26; // 0x1a + field public static final int TXTR = 218; // 0xda field public static final int TZERO_TECHNOLOGIES = 81; // 0x51 + field public static final int UNIVERSAL_ELECTRONICS = 147; // 0x93 + field public static final int VERTU = 162; // 0xa2 + field public static final int VISTEON = 167; // 0xa7 field public static final int VIZIO = 88; // 0x58 + field public static final int VOYETRA_TURTLE_BEACH = 217; // 0xd9 field public static final int WAVEPLUS_TECHNOLOGY = 35; // 0x23 field public static final int WICENTRIC = 95; // 0x5f field public static final int WIDCOMM = 17; // 0x11 + field public static final int WUXI_VIMICRO = 129; // 0x81 field public static final int ZEEVO = 18; // 0x12 + field public static final int ZER01_TV = 152; // 0x98 + field public static final int ZOMM = 116; // 0x74 + field public static final int ZSCAN_SOFTWARE = 141; // 0x8d } public final class BluetoothClass implements android.os.Parcelable { @@ -4867,6 +5109,7 @@ package android.bluetooth { public final class BluetoothDevice implements android.os.Parcelable { method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback); + method public boolean createBond(); method public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException; method public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException; method public int describeContents(); @@ -4877,6 +5120,8 @@ package android.bluetooth { method public java.lang.String getName(); method public int getType(); method public android.os.ParcelUuid[] getUuids(); + method public boolean setPairingConfirmation(boolean); + method public boolean setPin(byte[]); method public void writeToParcel(android.os.Parcel, int); field public static final java.lang.String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED"; field public static final java.lang.String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED"; @@ -4885,6 +5130,7 @@ package android.bluetooth { field public static final java.lang.String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED"; field public static final java.lang.String ACTION_FOUND = "android.bluetooth.device.action.FOUND"; field public static final java.lang.String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED"; + field public static final java.lang.String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST"; field public static final java.lang.String ACTION_UUID = "android.bluetooth.device.action.UUID"; field public static final int BOND_BONDED = 12; // 0xc field public static final int BOND_BONDING = 11; // 0xb @@ -4899,13 +5145,18 @@ package android.bluetooth { field public static final java.lang.String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS"; field public static final java.lang.String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE"; field public static final java.lang.String EXTRA_NAME = "android.bluetooth.device.extra.NAME"; + field public static final java.lang.String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; + field public static final java.lang.String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT"; field public static final java.lang.String EXTRA_PREVIOUS_BOND_STATE = "android.bluetooth.device.extra.PREVIOUS_BOND_STATE"; field public static final java.lang.String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI"; field public static final java.lang.String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; + field public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; // 0x2 + field public static final int PAIRING_VARIANT_PIN = 0; // 0x0 } public final class BluetoothGatt implements android.bluetooth.BluetoothProfile { - method public void abortReliableWrite(android.bluetooth.BluetoothDevice); + method public void abortReliableWrite(); + method public deprecated void abortReliableWrite(android.bluetooth.BluetoothDevice); method public boolean beginReliableWrite(); method public void close(); method public boolean connect(); @@ -5066,6 +5317,7 @@ package android.bluetooth { method public int getConnectionState(android.bluetooth.BluetoothDevice); method public java.util.List getDevicesMatchingConnectionStates(int[]); method public boolean isAudioConnected(android.bluetooth.BluetoothDevice); + method public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, java.lang.String, java.lang.String); method public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice); method public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice); field public static final java.lang.String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; @@ -5082,6 +5334,7 @@ package android.bluetooth { field public static final int STATE_AUDIO_CONNECTED = 12; // 0xc field public static final int STATE_AUDIO_CONNECTING = 11; // 0xb field public static final int STATE_AUDIO_DISCONNECTED = 10; // 0xa + field public static final java.lang.String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; field public static final java.lang.String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = "android.bluetooth.headset.intent.category.companyid"; } @@ -5376,8 +5629,10 @@ package android.content { method public void attachInfo(android.content.Context, android.content.pm.ProviderInfo); method public int bulkInsert(android.net.Uri, android.content.ContentValues[]); method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle); + method public android.net.Uri canonicalize(android.net.Uri); method public abstract int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); + method public final java.lang.String getCallingPackage(); method public final android.content.Context getContext(); method public final android.content.pm.PathPermission[] getPathPermissions(); method public final java.lang.String getReadPermission(); @@ -5391,16 +5646,20 @@ package android.content { method public void onLowMemory(); method public void onTrimMemory(int); method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method protected final android.os.ParcelFileDescriptor openFileHelper(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; method public android.os.ParcelFileDescriptor openPipeHelper(android.net.Uri, java.lang.String, android.os.Bundle, T, android.content.ContentProvider.PipeDataWriter) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; + method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method protected final void setPathPermissions(android.content.pm.PathPermission[]); method protected final void setReadPermission(java.lang.String); method protected final void setWritePermission(java.lang.String); method public void shutdown(); + method public android.net.Uri uncanonicalize(android.net.Uri); method public abstract int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } @@ -5412,17 +5671,22 @@ package android.content { method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList) throws android.content.OperationApplicationException, android.os.RemoteException; method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException; method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException; + method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException; method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException; method public android.content.ContentProvider getLocalContentProvider(); method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException; method public java.lang.String getType(android.net.Uri) throws android.os.RemoteException; method public android.net.Uri insert(android.net.Uri, android.content.ContentValues) throws android.os.RemoteException; method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException; + method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException; + method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException, android.os.RemoteException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException; method public boolean release(); + method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException; method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException; } @@ -5488,12 +5752,15 @@ package android.content { method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle); method public deprecated void cancelSync(android.net.Uri); method public static void cancelSync(android.accounts.Account, java.lang.String); + method public final android.net.Uri canonicalize(android.net.Uri); method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public static deprecated android.content.SyncInfo getCurrentSync(); method public static java.util.List getCurrentSyncs(); method public static int getIsSyncable(android.accounts.Account, java.lang.String); method public static boolean getMasterSyncAutomatically(); + method public java.util.List getOutgoingPersistedUriPermissions(); method public static java.util.List getPeriodicSyncs(android.accounts.Account, java.lang.String); + method public java.util.List getPersistedUriPermissions(); method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String); method public static android.content.SyncAdapterType[] getSyncAdapterTypes(); method public static boolean getSyncAutomatically(android.accounts.Account, java.lang.String); @@ -5504,21 +5771,28 @@ package android.content { method public void notifyChange(android.net.Uri, android.database.ContentObserver); method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean); method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final java.io.InputStream openInputStream(android.net.Uri) throws java.io.FileNotFoundException; method public final java.io.OutputStream openOutputStream(android.net.Uri) throws java.io.FileNotFoundException; method public final java.io.OutputStream openOutputStream(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method public final void registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver); + method public void releasePersistableUriPermission(android.net.Uri, int); method public static void removePeriodicSync(android.accounts.Account, java.lang.String, android.os.Bundle); method public static void removeStatusChangeListener(java.lang.Object); method public static void requestSync(android.accounts.Account, java.lang.String, android.os.Bundle); + method public static void requestSync(android.content.SyncRequest); method public static void setIsSyncable(android.accounts.Account, java.lang.String, int); method public static void setMasterSyncAutomatically(boolean); method public static void setSyncAutomatically(android.accounts.Account, java.lang.String, boolean); method public deprecated void startSync(android.net.Uri, android.os.Bundle); + method public void takePersistableUriPermission(android.net.Uri, int); + method public final android.net.Uri uncanonicalize(android.net.Uri); method public final void unregisterContentObserver(android.database.ContentObserver); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); method public static void validateSyncExtrasBundle(android.os.Bundle); @@ -5621,11 +5895,14 @@ package android.content { method public abstract java.io.File getDatabasePath(java.lang.String); method public abstract java.io.File getDir(java.lang.String, int); method public abstract java.io.File getExternalCacheDir(); + method public abstract java.io.File[] getExternalCacheDirs(); method public abstract java.io.File getExternalFilesDir(java.lang.String); + method public abstract java.io.File[] getExternalFilesDirs(java.lang.String); method public abstract java.io.File getFileStreamPath(java.lang.String); method public abstract java.io.File getFilesDir(); method public abstract android.os.Looper getMainLooper(); method public abstract java.io.File getObbDir(); + method public abstract java.io.File[] getObbDirs(); method public abstract java.lang.String getPackageCodePath(); method public abstract android.content.pm.PackageManager getPackageManager(); method public abstract java.lang.String getPackageName(); @@ -5687,6 +5964,7 @@ package android.content { field public static final java.lang.String ACCOUNT_SERVICE = "account"; field public static final java.lang.String ACTIVITY_SERVICE = "activity"; field public static final java.lang.String ALARM_SERVICE = "alarm"; + field public static final java.lang.String APP_OPS_SERVICE = "appops"; field public static final java.lang.String AUDIO_SERVICE = "audio"; field public static final int BIND_ABOVE_CLIENT = 8; // 0x8 field public static final int BIND_ADJUST_WITH_ACTIVITY = 128; // 0x80 @@ -5697,8 +5975,10 @@ package android.content { field public static final int BIND_NOT_FOREGROUND = 4; // 0x4 field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20 field public static final java.lang.String BLUETOOTH_SERVICE = "bluetooth"; + field public static final java.lang.String CAPTIONING_SERVICE = "captioning"; field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard"; field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity"; + field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir"; field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2 field public static final int CONTEXT_INCLUDE_CODE = 1; // 0x1 field public static final int CONTEXT_RESTRICTED = 4; // 0x4 @@ -5722,6 +6002,7 @@ package android.content { field public static final java.lang.String NOTIFICATION_SERVICE = "notification"; field public static final java.lang.String NSD_SERVICE = "servicediscovery"; field public static final java.lang.String POWER_SERVICE = "power"; + field public static final java.lang.String PRINT_SERVICE = "print"; field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String STORAGE_SERVICE = "storage"; @@ -5773,11 +6054,14 @@ package android.content { method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); + method public java.io.File[] getExternalCacheDirs(); method public java.io.File getExternalFilesDir(java.lang.String); + method public java.io.File[] getExternalFilesDirs(java.lang.String); method public java.io.File getFileStreamPath(java.lang.String); method public java.io.File getFilesDir(); method public android.os.Looper getMainLooper(); method public java.io.File getObbDir(); + method public java.io.File[] getObbDirs(); method public java.lang.String getPackageCodePath(); method public android.content.pm.PackageManager getPackageManager(); method public java.lang.String getPackageName(); @@ -6043,6 +6327,7 @@ package android.content { field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER"; field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS"; field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; + field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; field public static final java.lang.String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT"; field public static final java.lang.String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED"; field public static final java.lang.String ACTION_DEFAULT = "android.intent.action.VIEW"; @@ -6085,6 +6370,7 @@ package android.content { field public static final java.lang.String ACTION_MEDIA_UNMOUNTED = "android.intent.action.MEDIA_UNMOUNTED"; field public static final java.lang.String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED"; field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"; + field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT"; field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED"; field public static final java.lang.String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED"; field public static final java.lang.String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED"; @@ -6192,6 +6478,7 @@ package android.content { field public static final java.lang.String EXTRA_INTENT = "android.intent.extra.INTENT"; field public static final java.lang.String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT"; field public static final java.lang.String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; + field public static final java.lang.String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES"; field public static final java.lang.String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE"; field public static final java.lang.String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI"; field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"; @@ -6206,6 +6493,7 @@ package android.content { field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE"; field public static final java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT"; field public static final java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME"; + field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY"; field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM"; field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT"; field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE"; @@ -6240,10 +6528,12 @@ package android.content { field public static final int FLAG_DEBUG_LOG_RESOLUTION = 8; // 0x8 field public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 16; // 0x10 field public static final int FLAG_FROM_BACKGROUND = 4; // 0x4 + field public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 64; // 0x40 field public static final int FLAG_GRANT_READ_URI_PERMISSION = 1; // 0x1 field public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 2; // 0x2 field public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; // 0x20 field public static final int FLAG_RECEIVER_FOREGROUND = 268435456; // 0x10000000 + field public static final int FLAG_RECEIVER_NO_ABORT = 134217728; // 0x8000000 field public static final int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; // 0x40000000 field public static final int FLAG_RECEIVER_REPLACE_PENDING = 536870912; // 0x20000000 field public static final java.lang.String METADATA_DOCK_HOME = "android.dock_home"; @@ -6276,6 +6566,7 @@ package android.content { method public final void addDataAuthority(java.lang.String, java.lang.String); method public final void addDataPath(java.lang.String, int); method public final void addDataScheme(java.lang.String); + method public final void addDataSchemeSpecificPart(java.lang.String, int); method public final void addDataType(java.lang.String) throws android.content.IntentFilter.MalformedMimeTypeException; method public final java.util.Iterator authoritiesIterator(); method public final java.util.Iterator categoriesIterator(); @@ -6283,6 +6574,7 @@ package android.content { method public final int countCategories(); method public final int countDataAuthorities(); method public final int countDataPaths(); + method public final int countDataSchemeSpecificParts(); method public final int countDataSchemes(); method public final int countDataTypes(); method public static android.content.IntentFilter create(java.lang.String, java.lang.String); @@ -6293,6 +6585,7 @@ package android.content { method public final android.content.IntentFilter.AuthorityEntry getDataAuthority(int); method public final android.os.PatternMatcher getDataPath(int); method public final java.lang.String getDataScheme(int); + method public final android.os.PatternMatcher getDataSchemeSpecificPart(int); method public final java.lang.String getDataType(int); method public final int getPriority(); method public final boolean hasAction(java.lang.String); @@ -6300,6 +6593,7 @@ package android.content { method public final boolean hasDataAuthority(android.net.Uri); method public final boolean hasDataPath(java.lang.String); method public final boolean hasDataScheme(java.lang.String); + method public final boolean hasDataSchemeSpecificPart(java.lang.String); method public final boolean hasDataType(java.lang.String); method public final int match(android.content.ContentResolver, android.content.Intent, boolean, java.lang.String); method public final int match(java.lang.String, java.lang.String, java.lang.String, android.net.Uri, java.util.Set, java.lang.String); @@ -6309,6 +6603,7 @@ package android.content { method public final int matchDataAuthority(android.net.Uri); method public final java.util.Iterator pathsIterator(); method public void readFromXml(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public final java.util.Iterator schemeSpecificPartsIterator(); method public final java.util.Iterator schemesIterator(); method public final void setPriority(int); method public final java.util.Iterator typesIterator(); @@ -6323,6 +6618,7 @@ package android.content { field public static final int MATCH_CATEGORY_PATH = 5242880; // 0x500000 field public static final int MATCH_CATEGORY_PORT = 4194304; // 0x400000 field public static final int MATCH_CATEGORY_SCHEME = 2097152; // 0x200000 + field public static final int MATCH_CATEGORY_SCHEME_SPECIFIC_PART = 5767168; // 0x580000 field public static final int MATCH_CATEGORY_TYPE = 6291456; // 0x600000 field public static final int NO_MATCH_ACTION = -3; // 0xfffffffd field public static final int NO_MATCH_CATEGORY = -4; // 0xfffffffc @@ -6555,6 +6851,27 @@ package android.content { field public final long startTime; } + public class SyncRequest implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class SyncRequest.Builder { + ctor public SyncRequest.Builder(); + method public android.content.SyncRequest build(); + method public android.content.SyncRequest.Builder setDisallowMetered(boolean); + method public android.content.SyncRequest.Builder setExpedited(boolean); + method public android.content.SyncRequest.Builder setExtras(android.os.Bundle); + method public android.content.SyncRequest.Builder setIgnoreBackoff(boolean); + method public android.content.SyncRequest.Builder setIgnoreSettings(boolean); + method public android.content.SyncRequest.Builder setManual(boolean); + method public android.content.SyncRequest.Builder setNoRetry(boolean); + method public android.content.SyncRequest.Builder setSyncAdapter(android.accounts.Account, java.lang.String); + method public android.content.SyncRequest.Builder syncOnce(); + method public android.content.SyncRequest.Builder syncPeriodic(long, long); + } + public final class SyncResult implements android.os.Parcelable { ctor public SyncResult(); method public void clear(); @@ -6607,6 +6924,17 @@ package android.content { field public static final int NO_MATCH = -1; // 0xffffffff } + public final class UriPermission implements android.os.Parcelable { + method public int describeContents(); + method public long getPersistedTime(); + method public android.net.Uri getUri(); + method public boolean isReadPermission(); + method public boolean isWritePermission(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final long INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L + } + } package android.content.pm { @@ -6744,6 +7072,7 @@ package android.content.pm { ctor public ComponentInfo(android.content.pm.ComponentInfo); ctor protected ComponentInfo(android.os.Parcel); method public final int getIconResource(); + method public final int getLogoResource(); method public boolean isEnabled(); field public android.content.pm.ApplicationInfo applicationInfo; field public int descriptionRes; @@ -6925,6 +7254,7 @@ package android.content.pm { method public abstract java.util.List queryInstrumentation(java.lang.String, int); method public abstract java.util.List queryIntentActivities(android.content.Intent, int); method public abstract java.util.List queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int); + method public abstract java.util.List queryIntentContentProviders(android.content.Intent, int); method public abstract java.util.List queryIntentServices(android.content.Intent, int); method public abstract java.util.List queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract deprecated void removePackageFromPreferred(java.lang.String); @@ -6953,6 +7283,8 @@ package android.content.pm { field public static final java.lang.String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus"; field public static final java.lang.String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash"; field public static final java.lang.String FEATURE_CAMERA_FRONT = "android.hardware.camera.front"; + field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir"; + field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand"; @@ -6964,6 +7296,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_LOCATION_NETWORK = "android.hardware.location.network"; field public static final java.lang.String FEATURE_MICROPHONE = "android.hardware.microphone"; field public static final java.lang.String FEATURE_NFC = "android.hardware.nfc"; + field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce"; field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape"; field public static final java.lang.String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait"; field public static final java.lang.String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer"; @@ -6972,6 +7305,8 @@ package android.content.pm { field public static final java.lang.String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope"; field public static final java.lang.String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light"; field public static final java.lang.String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity"; + field public static final java.lang.String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter"; + field public static final java.lang.String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector"; field public static final java.lang.String FEATURE_SIP = "android.software.sip"; field public static final java.lang.String FEATURE_SIP_VOIP = "android.software.sip.voip"; field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony"; @@ -7087,6 +7422,7 @@ package android.content.pm { ctor public ProviderInfo(); ctor public ProviderInfo(android.content.pm.ProviderInfo); method public int describeContents(); + method public void dump(android.util.Printer, java.lang.String); field public static final android.os.Parcelable.Creator CREATOR; field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public java.lang.String authority; @@ -7120,6 +7456,7 @@ package android.content.pm { field public java.lang.CharSequence nonLocalizedLabel; field public int preferredOrder; field public int priority; + field public android.content.pm.ProviderInfo providerInfo; field public java.lang.String resolvePackageName; field public android.content.pm.ServiceInfo serviceInfo; field public int specificIndex; @@ -7159,13 +7496,15 @@ package android.content.pm { package android.content.res { - public class AssetFileDescriptor implements android.os.Parcelable { + public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable { ctor public AssetFileDescriptor(android.os.ParcelFileDescriptor, long, long); + ctor public AssetFileDescriptor(android.os.ParcelFileDescriptor, long, long, android.os.Bundle); method public void close() throws java.io.IOException; method public java.io.FileInputStream createInputStream() throws java.io.IOException; method public java.io.FileOutputStream createOutputStream() throws java.io.IOException; method public int describeContents(); method public long getDeclaredLength(); + method public android.os.Bundle getExtras(); method public java.io.FileDescriptor getFileDescriptor(); method public long getLength(); method public android.os.ParcelFileDescriptor getParcelFileDescriptor(); @@ -7432,7 +7771,7 @@ package android.content.res { method public void recycle(); } - public abstract interface XmlResourceParser implements android.util.AttributeSet org.xmlpull.v1.XmlPullParser { + public abstract interface XmlResourceParser implements android.util.AttributeSet java.lang.AutoCloseable org.xmlpull.v1.XmlPullParser { method public abstract void close(); } @@ -7572,6 +7911,7 @@ package android.database { method public abstract float getFloat(int); method public abstract int getInt(int); method public abstract long getLong(int); + method public abstract android.net.Uri getNotificationUri(); method public abstract int getPosition(); method public abstract short getShort(int); method public abstract java.lang.String getString(int); @@ -7677,6 +8017,7 @@ package android.database { method public float getFloat(int); method public int getInt(int); method public long getLong(int); + method public android.net.Uri getNotificationUri(); method public int getPosition(); method public short getShort(int); method public java.lang.String getString(int); @@ -7824,6 +8165,7 @@ package android.database { public class MatrixCursor.RowBuilder { method public android.database.MatrixCursor.RowBuilder add(java.lang.Object); + method public android.database.MatrixCursor.RowBuilder add(java.lang.String, java.lang.Object); } public class MergeCursor extends android.database.AbstractCursor { @@ -8568,6 +8910,7 @@ package android.graphics { method public void eraseColor(int); method public android.graphics.Bitmap extractAlpha(); method public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]); + method public final int getAllocationByteCount(); method public final int getByteCount(); method public final android.graphics.Bitmap.Config getConfig(); method public int getDensity(); @@ -8590,13 +8933,18 @@ package android.graphics { method public final boolean isPremultiplied(); method public final boolean isRecycled(); method public void prepareToDraw(); + method public void reconfigure(int, int, android.graphics.Bitmap.Config); method public void recycle(); method public boolean sameAs(android.graphics.Bitmap); + method public void setConfig(android.graphics.Bitmap.Config); method public void setDensity(int); method public void setHasAlpha(boolean); method public final void setHasMipMap(boolean); + method public void setHeight(int); method public void setPixel(int, int, int); method public void setPixels(int[], int, int, int, int, int, int); + method public final void setPremultiplied(boolean); + method public void setWidth(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int DENSITY_NONE = 0; // 0x0 @@ -8645,6 +8993,7 @@ package android.graphics { field public boolean inMutable; field public boolean inPreferQualityOverSpeed; field public android.graphics.Bitmap.Config inPreferredConfig; + field public boolean inPremultiplied; field public boolean inPurgeable; field public int inSampleSize; field public boolean inScaled; @@ -8905,6 +9254,7 @@ package android.graphics { field public static final int NV21 = 17; // 0x11 field public static final int RGB_565 = 4; // 0x4 field public static final int UNKNOWN = 0; // 0x0 + field public static final int YUV_420_888 = 35; // 0x23 field public static final int YUY2 = 20; // 0x14 field public static final int YV12 = 842094169; // 0x32315659 } @@ -9032,12 +9382,16 @@ package android.graphics { } public class NinePatch { + ctor public NinePatch(android.graphics.Bitmap, byte[]); ctor public NinePatch(android.graphics.Bitmap, byte[], java.lang.String); method public void draw(android.graphics.Canvas, android.graphics.RectF); method public void draw(android.graphics.Canvas, android.graphics.Rect); method public void draw(android.graphics.Canvas, android.graphics.Rect, android.graphics.Paint); + method public android.graphics.Bitmap getBitmap(); method public int getDensity(); method public int getHeight(); + method public java.lang.String getName(); + method public android.graphics.Paint getPaint(); method public final android.graphics.Region getTransparentRegion(android.graphics.Rect); method public int getWidth(); method public final boolean hasAlpha(); @@ -9138,6 +9492,7 @@ package android.graphics { field public static final int ANTI_ALIAS_FLAG = 1; // 0x1 field public static final int DEV_KERN_TEXT_FLAG = 256; // 0x100 field public static final int DITHER_FLAG = 4; // 0x4 + field public static final int EMBEDDED_BITMAP_TEXT_FLAG = 1024; // 0x400 field public static final int FAKE_BOLD_TEXT_FLAG = 32; // 0x20 field public static final int FILTER_BITMAP_FLAG = 2; // 0x2 field public static final int HINTING_OFF = 0; // 0x0 @@ -9229,6 +9584,8 @@ package android.graphics { method public void moveTo(float, float); method public void offset(float, float, android.graphics.Path); method public void offset(float, float); + method public boolean op(android.graphics.Path, android.graphics.Path.Op); + method public boolean op(android.graphics.Path, android.graphics.Path, android.graphics.Path.Op); method public void quadTo(float, float, float, float); method public void rCubicTo(float, float, float, float, float, float); method public void rLineTo(float, float); @@ -9260,6 +9617,16 @@ package android.graphics { enum_constant public static final android.graphics.Path.FillType WINDING; } + public static final class Path.Op extends java.lang.Enum { + method public static android.graphics.Path.Op valueOf(java.lang.String); + method public static final android.graphics.Path.Op[] values(); + enum_constant public static final android.graphics.Path.Op DIFFERENCE; + enum_constant public static final android.graphics.Path.Op INTERSECT; + enum_constant public static final android.graphics.Path.Op REVERSE_DIFFERENCE; + enum_constant public static final android.graphics.Path.Op UNION; + enum_constant public static final android.graphics.Path.Op XOR; + } + public class PathDashPathEffect extends android.graphics.PathEffect { ctor public PathDashPathEffect(android.graphics.Path, float, float, android.graphics.PathDashPathEffect.Style); } @@ -9306,10 +9673,10 @@ package android.graphics { ctor public PixelFormat(); method public static boolean formatHasAlpha(int); method public static void getPixelFormatInfo(int, android.graphics.PixelFormat); - field public static final int A_8 = 8; // 0x8 + field public static final deprecated int A_8 = 8; // 0x8 field public static final deprecated int JPEG = 256; // 0x100 field public static final deprecated int LA_88 = 10; // 0xa - field public static final int L_8 = 9; // 0x9 + field public static final deprecated int L_8 = 9; // 0x9 field public static final int OPAQUE = -1; // 0xffffffff field public static final deprecated int RGBA_4444 = 7; // 0x7 field public static final deprecated int RGBA_5551 = 6; // 0x6 @@ -9567,11 +9934,13 @@ package android.graphics { public class SurfaceTexture { ctor public SurfaceTexture(int); + ctor public SurfaceTexture(int, boolean); method public void attachToGLContext(int); method public void detachFromGLContext(); method public long getTimestamp(); method public void getTransformMatrix(float[]); method public void release(); + method public void releaseTexImage(); method public void setDefaultBufferSize(int, int); method public void setOnFrameAvailableListener(android.graphics.SurfaceTexture.OnFrameAvailableListener); method public void updateTexImage(); @@ -9581,7 +9950,7 @@ package android.graphics { method public abstract void onFrameAvailable(android.graphics.SurfaceTexture); } - public static class SurfaceTexture.OutOfResourcesException extends java.lang.Exception { + public static deprecated class SurfaceTexture.OutOfResourcesException extends java.lang.Exception { ctor public SurfaceTexture.OutOfResourcesException(); ctor public SurfaceTexture.OutOfResourcesException(java.lang.String); } @@ -9669,6 +10038,7 @@ package android.graphics.drawable { method public android.graphics.Shader.TileMode getTileModeY(); method public boolean hasAntiAlias(); method public boolean hasMipMap(); + method public final boolean isAutoMirrored(); method public void setAlpha(int); method public void setAntiAlias(boolean); method public void setColorFilter(android.graphics.ColorFilter); @@ -9699,7 +10069,6 @@ package android.graphics.drawable { ctor public ColorDrawable(); ctor public ColorDrawable(int); method public void draw(android.graphics.Canvas); - method public int getAlpha(); method public int getColor(); method public int getOpacity(); method public void setAlpha(int); @@ -9719,6 +10088,7 @@ package android.graphics.drawable { method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.graphics.drawable.Drawable createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public abstract void draw(android.graphics.Canvas); + method public int getAlpha(); method public final android.graphics.Rect getBounds(); method public android.graphics.drawable.Drawable.Callback getCallback(); method public int getChangingConfigurations(); @@ -9735,6 +10105,7 @@ package android.graphics.drawable { method public android.graphics.Region getTransparentRegion(); method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public void invalidateSelf(); + method public boolean isAutoMirrored(); method public boolean isStateful(); method public final boolean isVisible(); method public void jumpToCurrentState(); @@ -9745,6 +10116,7 @@ package android.graphics.drawable { method public static int resolveOpacity(int, int); method public void scheduleSelf(java.lang.Runnable, long); method public abstract void setAlpha(int); + method public void setAutoMirrored(boolean); method public void setBounds(int, int, int, int); method public void setBounds(android.graphics.Rect); method public final void setCallback(android.graphics.drawable.Drawable.Callback); @@ -9792,6 +10164,7 @@ package android.graphics.drawable { method public synchronized boolean canConstantState(); method protected void computeConstantSize(); method public int getChangingConfigurations(); + method public final android.graphics.drawable.Drawable getChild(int); method public final int getChildCount(); method public final android.graphics.drawable.Drawable[] getChildren(); method public final int getConstantHeight(); @@ -9858,6 +10231,7 @@ package android.graphics.drawable { ctor public InsetDrawable(android.graphics.drawable.Drawable, int); ctor public InsetDrawable(android.graphics.drawable.Drawable, int, int, int, int); method public void draw(android.graphics.Canvas); + method public android.graphics.drawable.Drawable getDrawable(); method public int getOpacity(); method public void invalidateDrawable(android.graphics.drawable.Drawable); method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long); @@ -10025,6 +10399,37 @@ package android.graphics.drawable.shapes { } +package android.graphics.pdf { + + public class PdfDocument { + ctor public PdfDocument(); + method public void close(); + method public void finishPage(android.graphics.pdf.PdfDocument.Page); + method public java.util.List getPages(); + method public android.graphics.pdf.PdfDocument.Page startPage(android.graphics.pdf.PdfDocument.PageInfo); + method public void writeTo(java.io.OutputStream) throws java.io.IOException; + } + + public static final class PdfDocument.Page { + method public android.graphics.Canvas getCanvas(); + method public android.graphics.pdf.PdfDocument.PageInfo getInfo(); + } + + public static final class PdfDocument.PageInfo { + method public android.graphics.Rect getContentRect(); + method public int getPageHeight(); + method public int getPageNumber(); + method public int getPageWidth(); + } + + public static final class PdfDocument.PageInfo.Builder { + ctor public PdfDocument.PageInfo.Builder(int, int, int); + method public android.graphics.pdf.PdfDocument.PageInfo create(); + method public android.graphics.pdf.PdfDocument.PageInfo.Builder setContentRect(android.graphics.Rect); + } + +} + package android.hardware { public class Camera { @@ -10279,6 +10684,18 @@ package android.hardware { field public int width; } + public final class ConsumerIrManager { + method public android.hardware.ConsumerIrManager.CarrierFrequencyRange[] getCarrierFrequencies(); + method public boolean hasIrEmitter(); + method public void transmit(int, int[]); + } + + public final class ConsumerIrManager.CarrierFrequencyRange { + ctor public ConsumerIrManager.CarrierFrequencyRange(int, int); + method public int getMaxFrequency(); + method public int getMinFrequency(); + } + public class GeomagneticField { ctor public GeomagneticField(float, float, float, long); method public float getDeclination(); @@ -10291,6 +10708,8 @@ package android.hardware { } public final class Sensor { + method public int getFifoMaxEventCount(); + method public int getFifoReservedEventCount(); method public float getMaximumRange(); method public int getMinDelay(); method public java.lang.String getName(); @@ -10303,6 +10722,7 @@ package android.hardware { field public static final int TYPE_ALL = -1; // 0xffffffff field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf + field public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; // 0x14 field public static final int TYPE_GRAVITY = 9; // 0x9 field public static final int TYPE_GYROSCOPE = 4; // 0x4 field public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; // 0x10 @@ -10316,6 +10736,8 @@ package android.hardware { field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc field public static final int TYPE_ROTATION_VECTOR = 11; // 0xb field public static final int TYPE_SIGNIFICANT_MOTION = 17; // 0x11 + field public static final int TYPE_STEP_COUNTER = 19; // 0x13 + field public static final int TYPE_STEP_DETECTOR = 18; // 0x12 field public static final deprecated int TYPE_TEMPERATURE = 7; // 0x7 } @@ -10331,6 +10753,10 @@ package android.hardware { method public abstract void onSensorChanged(android.hardware.SensorEvent); } + public abstract interface SensorEventListener2 implements android.hardware.SensorEventListener { + method public abstract void onFlushCompleted(android.hardware.Sensor); + } + public abstract deprecated interface SensorListener { method public abstract void onAccuracyChanged(int, int); method public abstract void onSensorChanged(int, float[]); @@ -10338,6 +10764,7 @@ package android.hardware { public abstract class SensorManager { method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor); + method public boolean flush(android.hardware.SensorEventListener); method public static float getAltitude(float, float); method public static void getAngleChange(float[], float[], float[]); method public android.hardware.Sensor getDefaultSensor(int); @@ -10351,7 +10778,9 @@ package android.hardware { method public deprecated boolean registerListener(android.hardware.SensorListener, int); method public deprecated boolean registerListener(android.hardware.SensorListener, int, int); method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int); + method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int); method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, android.os.Handler); + method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int, android.os.Handler); method public static boolean remapCoordinateSystem(float[], int, int, float[]); method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor); method public deprecated void unregisterListener(android.hardware.SensorListener); @@ -10433,12 +10862,16 @@ package android.hardware { package android.hardware.display { public final class DisplayManager { + method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, android.view.Surface, int); method public android.view.Display getDisplay(int); method public android.view.Display[] getDisplays(); method public android.view.Display[] getDisplays(java.lang.String); method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler); method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener); field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION"; + field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2 + field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1 + field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4 } public static abstract interface DisplayManager.DisplayListener { @@ -10447,6 +10880,11 @@ package android.hardware.display { method public abstract void onDisplayRemoved(int); } + public final class VirtualDisplay { + method public android.view.Display getDisplay(); + method public void release(); + } + } package android.hardware.input { @@ -11154,6 +11592,7 @@ package android.location { field public static final java.lang.String KEY_PROVIDER_ENABLED = "providerEnabled"; field public static final java.lang.String KEY_PROXIMITY_ENTERING = "entering"; field public static final java.lang.String KEY_STATUS_CHANGED = "status"; + field public static final java.lang.String MODE_CHANGED_ACTION = "android.location.MODE_CHANGED"; field public static final java.lang.String NETWORK_PROVIDER = "network"; field public static final java.lang.String PASSIVE_PROVIDER = "passive"; field public static final java.lang.String PROVIDERS_CHANGED_ACTION = "android.location.PROVIDERS_CHANGED"; @@ -11176,6 +11615,19 @@ package android.location { field public static final int TEMPORARILY_UNAVAILABLE = 1; // 0x1 } + public abstract class SettingInjectorService extends android.app.Service { + ctor public SettingInjectorService(java.lang.String); + method public final android.os.IBinder onBind(android.content.Intent); + method protected abstract boolean onGetEnabled(); + method protected abstract java.lang.String onGetSummary(); + method public final void onStart(android.content.Intent, int); + method public final int onStartCommand(android.content.Intent, int, int); + field public static final java.lang.String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged"; + field public static final java.lang.String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService"; + field public static final java.lang.String ATTRIBUTES_NAME = "injected-location-setting"; + field public static final java.lang.String META_DATA_NAME = "android.location.SettingInjectorService"; + } + } package android.media { @@ -11237,6 +11689,7 @@ package android.media { method public void adjustStreamVolume(int, int, int); method public void adjustSuggestedStreamVolume(int, int, int); method public void adjustVolume(int, int); + method public void dispatchMediaKeyEvent(android.view.KeyEvent); method public int getMode(); method public java.lang.String getParameters(java.lang.String); method public java.lang.String getProperty(java.lang.String); @@ -11258,6 +11711,7 @@ package android.media { method public void registerMediaButtonEventReceiver(android.content.ComponentName); method public void registerMediaButtonEventReceiver(android.app.PendingIntent); method public void registerRemoteControlClient(android.media.RemoteControlClient); + method public boolean registerRemoteController(android.media.RemoteController); method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int); method public deprecated void setBluetoothA2dpOn(boolean); method public void setBluetoothScoOn(boolean); @@ -11279,6 +11733,7 @@ package android.media { method public void unregisterMediaButtonEventReceiver(android.content.ComponentName); method public void unregisterMediaButtonEventReceiver(android.app.PendingIntent); method public void unregisterRemoteControlClient(android.media.RemoteControlClient); + method public void unregisterRemoteController(android.media.RemoteController); field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY"; field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED"; @@ -11287,6 +11742,7 @@ package android.media { field public static final int ADJUST_SAME = 0; // 0x0 field public static final int AUDIOFOCUS_GAIN = 1; // 0x1 field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2 + field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4 field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3 field public static final int AUDIOFOCUS_LOSS = -1; // 0xffffffff field public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; // 0xfffffffe @@ -11308,6 +11764,7 @@ package android.media { field public static final int FX_FOCUS_NAVIGATION_RIGHT = 4; // 0x4 field public static final int FX_FOCUS_NAVIGATION_UP = 1; // 0x1 field public static final int FX_KEYPRESS_DELETE = 7; // 0x7 + field public static final int FX_KEYPRESS_INVALID = 9; // 0x9 field public static final int FX_KEYPRESS_RETURN = 8; // 0x8 field public static final int FX_KEYPRESS_SPACEBAR = 6; // 0x6 field public static final int FX_KEYPRESS_STANDARD = 5; // 0x5 @@ -11395,6 +11852,12 @@ package android.media { method public abstract void onPeriodicNotification(android.media.AudioRecord); } + public final class AudioTimestamp { + ctor public AudioTimestamp(); + field public long framePosition; + field public long nanoTime; + } + public class AudioTrack { ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException; ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException; @@ -11407,7 +11870,7 @@ package android.media { method public static float getMaxVolume(); method public static int getMinBufferSize(int, int, int); method public static float getMinVolume(); - method protected int getNativeFrameCount(); + method protected deprecated int getNativeFrameCount(); method public static int getNativeOutputSampleRate(int); method public int getNotificationMarkerPosition(); method public int getPlayState(); @@ -11417,6 +11880,7 @@ package android.media { method public int getSampleRate(); method public int getState(); method public int getStreamType(); + method public boolean getTimestamp(android.media.AudioTimestamp); method public void pause() throws java.lang.IllegalStateException; method public void play() throws java.lang.IllegalStateException; method public void release(); @@ -11429,7 +11893,7 @@ package android.media { method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener, android.os.Handler); method public int setPlaybackRate(int); method public int setPositionNotificationPeriod(int); - method protected void setState(int); + method protected deprecated void setState(int); method public int setStereoVolume(float, float); method public void stop() throws java.lang.IllegalStateException; method public int write(byte[], int, int); @@ -11562,6 +12026,38 @@ package android.media { field public static final int EULER_Z = 2; // 0x2 } + public abstract class Image implements java.lang.AutoCloseable { + method public abstract void close(); + method public abstract int getFormat(); + method public abstract int getHeight(); + method public abstract android.media.Image.Plane[] getPlanes(); + method public abstract long getTimestamp(); + method public abstract int getWidth(); + } + + public static abstract class Image.Plane { + method public abstract java.nio.ByteBuffer getBuffer(); + method public abstract int getPixelStride(); + method public abstract int getRowStride(); + } + + public class ImageReader implements java.lang.AutoCloseable { + method public android.media.Image acquireLatestImage(); + method public android.media.Image acquireNextImage(); + method public void close(); + method public int getHeight(); + method public int getImageFormat(); + method public int getMaxImages(); + method public android.view.Surface getSurface(); + method public int getWidth(); + method public static android.media.ImageReader newInstance(int, int, int, int); + method public void setOnImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler); + } + + public static abstract interface ImageReader.OnImageAvailableListener { + method public abstract void onImageAvailable(android.media.ImageReader); + } + public class JetPlayer { method public boolean clearQueue(); method public java.lang.Object clone() throws java.lang.CloneNotSupportedException; @@ -11619,6 +12115,7 @@ package android.media { method public final void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; method public final void release(); method public final void releaseOutputBuffer(int, boolean); + method public final void setParameters(android.os.Bundle); method public final void setVideoScalingMode(int); method public final void signalEndOfInputStream(); method public final void start(); @@ -11632,6 +12129,9 @@ package android.media { field public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff + field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; + field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames"; + field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate"; field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1 field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2 } @@ -11648,6 +12148,9 @@ package android.media { public static final class MediaCodec.CryptoException extends java.lang.RuntimeException { ctor public MediaCodec.CryptoException(int, java.lang.String); method public int getErrorCode(); + field public static final int ERROR_KEY_EXPIRED = 2; // 0x2 + field public static final int ERROR_NO_KEY = 1; // 0x1 + field public static final int ERROR_RESOURCE_BUSY = 3; // 0x3 } public static final class MediaCodec.CryptoInfo { @@ -11670,6 +12173,7 @@ package android.media { public static final class MediaCodecInfo.CodecCapabilities { ctor public MediaCodecInfo.CodecCapabilities(); + method public final boolean isFeatureSupported(java.lang.String); field public static final int COLOR_Format12bitRGB444 = 3; // 0x3 field public static final int COLOR_Format16bitARGB1555 = 5; // 0x5 field public static final int COLOR_Format16bitARGB4444 = 4; // 0x4 @@ -11716,6 +12220,7 @@ package android.media { field public static final int COLOR_FormatYUV444Interleaved = 29; // 0x1d field public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00 field public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100 + field public static final java.lang.String FEATURE_AdaptivePlayback = "adaptive-playback"; field public int[] colorFormats; field public android.media.MediaCodecInfo.CodecProfileLevel[] profileLevels; } @@ -11831,6 +12336,7 @@ package android.media { method public android.media.MediaDrm.ProvisionRequest getProvisionRequest(); method public java.util.List getSecureStops(); method public static final boolean isCryptoSchemeSupported(java.util.UUID); + method public static final boolean isCryptoSchemeSupported(java.util.UUID, java.lang.String); method public byte[] openSession() throws android.media.NotProvisionedException; method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException; method public void provideProvisionResponse(byte[]) throws android.media.DeniedByServerException; @@ -11914,6 +12420,7 @@ package android.media { ctor public MediaFormat(); method public final boolean containsKey(java.lang.String); method public static final android.media.MediaFormat createAudioFormat(java.lang.String, int, int); + method public static final android.media.MediaFormat createSubtitleFormat(java.lang.String, java.lang.String); method public static final android.media.MediaFormat createVideoFormat(java.lang.String, int, int); method public final java.nio.ByteBuffer getByteBuffer(java.lang.String); method public final float getFloat(java.lang.String); @@ -11935,13 +12442,40 @@ package android.media { field public static final java.lang.String KEY_FRAME_RATE = "frame-rate"; field public static final java.lang.String KEY_HEIGHT = "height"; field public static final java.lang.String KEY_IS_ADTS = "is-adts"; + field public static final java.lang.String KEY_IS_AUTOSELECT = "is-autoselect"; + field public static final java.lang.String KEY_IS_DEFAULT = "is-default"; + field public static final java.lang.String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; field public static final java.lang.String KEY_I_FRAME_INTERVAL = "i-frame-interval"; + field public static final java.lang.String KEY_LANGUAGE = "language"; + field public static final java.lang.String KEY_MAX_HEIGHT = "max-height"; field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size"; + field public static final java.lang.String KEY_MAX_WIDTH = "max-width"; field public static final java.lang.String KEY_MIME = "mime"; + field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; + field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate"; field public static final java.lang.String KEY_WIDTH = "width"; } + public abstract class MediaMetadataEditor { + method public synchronized void addEditableKey(int); + method public abstract void apply(); + method public synchronized void clear(); + method public synchronized android.graphics.Bitmap getBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException; + method public synchronized int[] getEditableKeys(); + method public synchronized long getLong(int, long) throws java.lang.IllegalArgumentException; + method public synchronized java.lang.Object getObject(int, java.lang.Object) throws java.lang.IllegalArgumentException; + method public synchronized java.lang.String getString(int, java.lang.String) throws java.lang.IllegalArgumentException; + method public synchronized android.media.MediaMetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException; + method public synchronized android.media.MediaMetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException; + method public synchronized android.media.MediaMetadataEditor putObject(int, java.lang.Object) throws java.lang.IllegalArgumentException; + method public synchronized android.media.MediaMetadataEditor putString(int, java.lang.String) throws java.lang.IllegalArgumentException; + method public synchronized void removeEditableKeys(); + field public static final int BITMAP_KEY_ARTWORK = 100; // 0x64 + field public static final int RATING_KEY_BY_OTHERS = 101; // 0x65 + field public static final int RATING_KEY_BY_USER = 268435457; // 0x10000001 + } + public class MediaMetadataRetriever { ctor public MediaMetadataRetriever(); method public java.lang.String extractMetadata(int); @@ -11988,6 +12522,7 @@ package android.media { ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException; method public int addTrack(android.media.MediaFormat); method public void release(); + method public void setLocation(float, float); method public void setOrientationHint(int); method public void start(); method public void stop(); @@ -12062,7 +12597,9 @@ package android.media { field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322 field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321 + field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386 field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1 + field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385 field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3 field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; @@ -12104,6 +12641,7 @@ package android.media { public static class MediaPlayer.TrackInfo implements android.os.Parcelable { method public int describeContents(); + method public android.media.MediaFormat getFormat(); method public java.lang.String getLanguage(); method public int getTrackType(); method public void writeToParcel(android.os.Parcel, int); @@ -12165,6 +12703,7 @@ package android.media { field public static final int CAMCORDER = 5; // 0x5 field public static final int DEFAULT = 0; // 0x0 field public static final int MIC = 1; // 0x1 + field public static final int REMOTE_SUBMIX = 8; // 0x8 field public static final int VOICE_CALL = 4; // 0x4 field public static final int VOICE_COMMUNICATION = 7; // 0x7 field public static final int VOICE_DOWNLINK = 3; // 0x3 @@ -12353,10 +12892,34 @@ package android.media { ctor public NotProvisionedException(java.lang.String); } + public final class Rating implements android.os.Parcelable { + method public int describeContents(); + method public float getPercentRating(); + method public int getRatingStyle(); + method public float getStarRating(); + method public boolean hasHeart(); + method public boolean isRated(); + method public boolean isThumbUp(); + method public static android.media.Rating newHeartRating(boolean); + method public static android.media.Rating newPercentageRating(float); + method public static android.media.Rating newStarRating(int, float); + method public static android.media.Rating newThumbRating(boolean); + method public static android.media.Rating newUnratedRating(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int RATING_3_STARS = 3; // 0x3 + field public static final int RATING_4_STARS = 4; // 0x4 + field public static final int RATING_5_STARS = 5; // 0x5 + field public static final int RATING_HEART = 1; // 0x1 + field public static final int RATING_PERCENTAGE = 6; // 0x6 + field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2 + } + public class RemoteControlClient { ctor public RemoteControlClient(android.app.PendingIntent); ctor public RemoteControlClient(android.app.PendingIntent, android.os.Looper); method public android.media.RemoteControlClient.MetadataEditor editMetadata(boolean); + method public void setMetadataUpdateListener(android.media.RemoteControlClient.OnMetadataUpdateListener); method public void setOnGetPlaybackPositionListener(android.media.RemoteControlClient.OnGetPlaybackPositionListener); method public void setPlaybackPositionUpdateListener(android.media.RemoteControlClient.OnPlaybackPositionUpdateListener); method public void setPlaybackState(int); @@ -12369,6 +12932,7 @@ package android.media { field public static final int FLAG_KEY_MEDIA_PLAY_PAUSE = 8; // 0x8 field public static final int FLAG_KEY_MEDIA_POSITION_UPDATE = 256; // 0x100 field public static final int FLAG_KEY_MEDIA_PREVIOUS = 1; // 0x1 + field public static final int FLAG_KEY_MEDIA_RATING = 512; // 0x200 field public static final int FLAG_KEY_MEDIA_REWIND = 2; // 0x2 field public static final int FLAG_KEY_MEDIA_STOP = 32; // 0x20 field public static final int PLAYSTATE_BUFFERING = 8; // 0x8 @@ -12382,12 +12946,8 @@ package android.media { field public static final int PLAYSTATE_STOPPED = 1; // 0x1 } - public class RemoteControlClient.MetadataEditor { + public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor { method public synchronized void apply(); - method public synchronized void clear(); - method public synchronized android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException; - method public synchronized android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException; - method public synchronized android.media.RemoteControlClient.MetadataEditor putString(int, java.lang.String) throws java.lang.IllegalArgumentException; field public static final int BITMAP_KEY_ARTWORK = 100; // 0x64 } @@ -12395,10 +12955,44 @@ package android.media { method public abstract long onGetPlaybackPosition(); } + public static abstract interface RemoteControlClient.OnMetadataUpdateListener { + method public abstract void onMetadataUpdate(int, java.lang.Object); + } + public static abstract interface RemoteControlClient.OnPlaybackPositionUpdateListener { method public abstract void onPlaybackPositionUpdate(long); } + public final class RemoteController { + ctor public RemoteController(android.content.Context, android.media.RemoteController.OnClientUpdateListener) throws java.lang.IllegalArgumentException; + ctor public RemoteController(android.content.Context, android.media.RemoteController.OnClientUpdateListener, android.os.Looper) throws java.lang.IllegalArgumentException; + method public boolean clearArtworkConfiguration(); + method public android.media.RemoteController.MetadataEditor editMetadata(); + method public long getEstimatedMediaPosition(); + method public boolean seekTo(long) throws java.lang.IllegalArgumentException; + method public boolean sendMediaKeyEvent(android.view.KeyEvent) throws java.lang.IllegalArgumentException; + method public boolean setArtworkConfiguration(int, int) throws java.lang.IllegalArgumentException; + method public boolean setSynchronizationMode(int) throws java.lang.IllegalArgumentException; + field public static final int POSITION_SYNCHRONIZATION_CHECK = 1; // 0x1 + field public static final int POSITION_SYNCHRONIZATION_NONE = 0; // 0x0 + } + + public class RemoteController.MetadataEditor extends android.media.MediaMetadataEditor { + method public synchronized void apply(); + } + + public static abstract interface RemoteController.OnClientUpdateListener { + method public abstract void onClientChange(boolean); + method public abstract void onClientMetadataUpdate(android.media.RemoteController.MetadataEditor); + method public abstract void onClientPlaybackStateUpdate(int); + method public abstract void onClientPlaybackStateUpdate(int, long, long, float); + method public abstract void onClientTransportControlUpdate(int); + } + + public final class ResourceBusyException extends android.media.MediaDrmException { + ctor public ResourceBusyException(java.lang.String); + } + public class Ringtone { method public int getStreamType(); method public java.lang.String getTitle(android.content.Context); @@ -12415,7 +13009,7 @@ package android.media { method public android.database.Cursor getCursor(); method public static int getDefaultType(android.net.Uri); method public static android.net.Uri getDefaultUri(int); - method public boolean getIncludeDrm(); + method public deprecated boolean getIncludeDrm(); method public android.media.Ringtone getRingtone(int); method public static android.media.Ringtone getRingtone(android.content.Context, android.net.Uri); method public int getRingtonePosition(android.net.Uri); @@ -12425,14 +13019,14 @@ package android.media { method public int inferStreamType(); method public static boolean isDefault(android.net.Uri); method public static void setActualDefaultRingtoneUri(android.content.Context, int, android.net.Uri); - method public void setIncludeDrm(boolean); + method public deprecated void setIncludeDrm(boolean); method public void setStopPreviousRingtone(boolean); method public void setType(int); method public void stopPreviousRingtone(); field public static final java.lang.String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER"; field public static final java.lang.String EXTRA_RINGTONE_DEFAULT_URI = "android.intent.extra.ringtone.DEFAULT_URI"; field public static final java.lang.String EXTRA_RINGTONE_EXISTING_URI = "android.intent.extra.ringtone.EXISTING_URI"; - field public static final java.lang.String EXTRA_RINGTONE_INCLUDE_DRM = "android.intent.extra.ringtone.INCLUDE_DRM"; + field public static final deprecated java.lang.String EXTRA_RINGTONE_INCLUDE_DRM = "android.intent.extra.ringtone.INCLUDE_DRM"; field public static final java.lang.String EXTRA_RINGTONE_PICKED_URI = "android.intent.extra.ringtone.PICKED_URI"; field public static final java.lang.String EXTRA_RINGTONE_SHOW_DEFAULT = "android.intent.extra.ringtone.SHOW_DEFAULT"; field public static final java.lang.String EXTRA_RINGTONE_SHOW_SILENT = "android.intent.extra.ringtone.SHOW_SILENT"; @@ -12789,6 +13383,12 @@ package android.media.audiofx { field public short numBands; } + public class LoudnessEnhancer extends android.media.audiofx.AudioEffect { + method public float getTargetGain() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method public void setTargetGain(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + field public static final int PARAM_TARGET_GAIN_MB = 0; // 0x0 + } + public class NoiseSuppressor extends android.media.audiofx.AudioEffect { method public static android.media.audiofx.NoiseSuppressor create(int); method public static boolean isAvailable(); @@ -12850,6 +13450,8 @@ package android.media.audiofx { method public boolean getEnabled(); method public int getFft(byte[]) throws java.lang.IllegalStateException; method public static int getMaxCaptureRate(); + method public int getMeasurementMode() throws java.lang.IllegalStateException; + method public int getMeasurementPeakRms(android.media.audiofx.Visualizer.MeasurementPeakRms); method public int getSamplingRate() throws java.lang.IllegalStateException; method public int getScalingMode() throws java.lang.IllegalStateException; method public int getWaveForm(byte[]) throws java.lang.IllegalStateException; @@ -12857,6 +13459,7 @@ package android.media.audiofx { method public int setCaptureSize(int) throws java.lang.IllegalStateException; method public int setDataCaptureListener(android.media.audiofx.Visualizer.OnDataCaptureListener, int, boolean, boolean); method public int setEnabled(boolean) throws java.lang.IllegalStateException; + method public int setMeasurementMode(int) throws java.lang.IllegalStateException; method public int setScalingMode(int) throws java.lang.IllegalStateException; field public static final int ALREADY_EXISTS = -2; // 0xfffffffe field public static final int ERROR = -1; // 0xffffffff @@ -12865,6 +13468,8 @@ package android.media.audiofx { field public static final int ERROR_INVALID_OPERATION = -5; // 0xfffffffb field public static final int ERROR_NO_INIT = -3; // 0xfffffffd field public static final int ERROR_NO_MEMORY = -6; // 0xfffffffa + field public static final int MEASUREMENT_MODE_NONE = 0; // 0x0 + field public static final int MEASUREMENT_MODE_PEAK_RMS = 1; // 0x1 field public static final int SCALING_MODE_AS_PLAYED = 1; // 0x1 field public static final int SCALING_MODE_NORMALIZED = 0; // 0x0 field public static final int STATE_ENABLED = 2; // 0x2 @@ -12873,6 +13478,12 @@ package android.media.audiofx { field public static final int SUCCESS = 0; // 0x0 } + public static final class Visualizer.MeasurementPeakRms { + ctor public Visualizer.MeasurementPeakRms(); + field public int mPeak; + field public int mRms; + } + public static abstract interface Visualizer.OnDataCaptureListener { method public abstract void onFftDataCapture(android.media.audiofx.Visualizer, byte[], int); method public abstract void onWaveFormDataCapture(android.media.audiofx.Visualizer, byte[], int); @@ -13105,7 +13716,7 @@ package android.net { method public int getUid(); } - public deprecated class DhcpInfo implements android.os.Parcelable { + public class DhcpInfo implements android.os.Parcelable { ctor public DhcpInfo(); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -13129,6 +13740,7 @@ package android.net { public class LocalSocket implements java.io.Closeable { ctor public LocalSocket(); + ctor public LocalSocket(int); method public void bind(android.net.LocalSocketAddress) throws java.io.IOException; method public void close() throws java.io.IOException; method public void connect(android.net.LocalSocketAddress) throws java.io.IOException; @@ -13154,6 +13766,9 @@ package android.net { method public void setSoTimeout(int) throws java.io.IOException; method public void shutdownInput() throws java.io.IOException; method public void shutdownOutput() throws java.io.IOException; + field public static final int SOCKET_DGRAM = 1; // 0x1 + field public static final int SOCKET_SEQPACKET = 3; // 0x3 + field public static final int SOCKET_STREAM = 2; // 0x2 } public class LocalSocketAddress { @@ -14013,7 +14628,7 @@ package android.net.wifi { method public boolean enableNetwork(int, boolean); method public java.util.List getConfiguredNetworks(); method public android.net.wifi.WifiInfo getConnectionInfo(); - method public deprecated android.net.DhcpInfo getDhcpInfo(); + method public android.net.DhcpInfo getDhcpInfo(); method public java.util.List getScanResults(); method public int getWifiState(); method public boolean isScanAlwaysAvailable(); @@ -14023,6 +14638,8 @@ package android.net.wifi { method public boolean reconnect(); method public boolean removeNetwork(int); method public boolean saveConfiguration(); + method public void setTdlsEnabled(java.net.InetAddress, boolean); + method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); method public boolean setWifiEnabled(boolean); method public boolean startScan(); method public int updateNetwork(android.net.wifi.WifiConfiguration); @@ -14339,8 +14956,10 @@ package android.nfc { public final class NfcAdapter { method public void disableForegroundDispatch(android.app.Activity); method public deprecated void disableForegroundNdefPush(android.app.Activity); + method public void disableReaderMode(android.app.Activity); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], java.lang.String[][]); method public deprecated void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage); + method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); method public boolean isEnabled(); method public boolean isNdefPushEnabled(); @@ -14356,7 +14975,15 @@ package android.nfc { field public static final java.lang.String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE"; field public static final java.lang.String EXTRA_ID = "android.nfc.extra.ID"; field public static final java.lang.String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES"; + field public static final java.lang.String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence"; field public static final java.lang.String EXTRA_TAG = "android.nfc.extra.TAG"; + field public static final int FLAG_READER_NFC_A = 1; // 0x1 + field public static final int FLAG_READER_NFC_B = 2; // 0x2 + field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10 + field public static final int FLAG_READER_NFC_F = 4; // 0x4 + field public static final int FLAG_READER_NFC_V = 8; // 0x8 + field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100 + field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80 field public static final int STATE_OFF = 1; // 0x1 field public static final int STATE_ON = 3; // 0x3 field public static final int STATE_TURNING_OFF = 4; // 0x4 @@ -14375,6 +15002,10 @@ package android.nfc { method public abstract void onNdefPushComplete(android.nfc.NfcEvent); } + public static abstract interface NfcAdapter.ReaderCallback { + method public abstract void onTagDiscovered(android.nfc.Tag); + } + public final class NfcEvent { field public final android.nfc.NfcAdapter nfcAdapter; } @@ -14398,6 +15029,45 @@ package android.nfc { } +package android.nfc.cardemulation { + + public final class CardEmulation { + method public static synchronized android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter); + method public int getSelectionModeForCategory(java.lang.String); + method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String); + method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String); + field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; + field public static final java.lang.String CATEGORY_OTHER = "other"; + field public static final java.lang.String CATEGORY_PAYMENT = "payment"; + field public static final java.lang.String EXTRA_CATEGORY = "category"; + field public static final java.lang.String EXTRA_SERVICE_COMPONENT = "component"; + field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1 + field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2 + field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 + } + + public abstract class HostApduService extends android.app.Service { + ctor public HostApduService(); + method public final void notifyUnhandled(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract void onDeactivated(int); + method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); + method public final void sendResponseApdu(byte[]); + field public static final int DEACTIVATION_DESELECTED = 1; // 0x1 + field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 + field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; + field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; + } + + public abstract class OffHostApduService extends android.app.Service { + ctor public OffHostApduService(); + method public abstract android.os.IBinder onBind(android.content.Intent); + field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"; + field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service"; + } + +} + package android.nfc.tech { abstract class BasicTagTechnology implements android.nfc.tech.TagTechnology { @@ -16613,7 +17283,7 @@ package android.opengl { } public class Matrix { - ctor public Matrix(); + ctor public deprecated Matrix(); method public static void frustumM(float[], int, float, float, float, float, float, float); method public static boolean invertM(float[], int, float[], int); method public static float length(float, float, float); @@ -16787,6 +17457,7 @@ package android.os { field public static final int JELLY_BEAN = 16; // 0x10 field public static final int JELLY_BEAN_MR1 = 17; // 0x11 field public static final int JELLY_BEAN_MR2 = 18; // 0x12 + field public static final int KITKAT = 19; // 0x13 } public final class Bundle implements java.lang.Cloneable android.os.Parcelable { @@ -16998,13 +17669,12 @@ package android.os { public static class Debug.MemoryInfo implements android.os.Parcelable { ctor public Debug.MemoryInfo(); method public int describeContents(); - method public static java.lang.String getOtherLabel(int); - method public int getOtherPrivateDirty(int); - method public int getOtherPss(int); - method public int getOtherSharedDirty(int); + method public int getTotalPrivateClean(); method public int getTotalPrivateDirty(); method public int getTotalPss(); + method public int getTotalSharedClean(); method public int getTotalSharedDirty(); + method public int getTotalSwappablePss(); method public void readFromParcel(android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -17059,10 +17729,12 @@ package android.os { method public static java.io.File getExternalStoragePublicDirectory(java.lang.String); method public static java.lang.String getExternalStorageState(); method public static java.io.File getRootDirectory(); + method public static java.lang.String getStorageState(java.io.File); method public static boolean isExternalStorageEmulated(); method public static boolean isExternalStorageRemovable(); field public static java.lang.String DIRECTORY_ALARMS; field public static java.lang.String DIRECTORY_DCIM; + field public static java.lang.String DIRECTORY_DOCUMENTS; field public static java.lang.String DIRECTORY_DOWNLOADS; field public static java.lang.String DIRECTORY_MOVIES; field public static java.lang.String DIRECTORY_MUSIC; @@ -17077,6 +17749,7 @@ package android.os { field public static final java.lang.String MEDIA_NOFS = "nofs"; field public static final java.lang.String MEDIA_REMOVED = "removed"; field public static final java.lang.String MEDIA_SHARED = "shared"; + field public static final java.lang.String MEDIA_UNKNOWN = "unknown"; field public static final java.lang.String MEDIA_UNMOUNTABLE = "unmountable"; field public static final java.lang.String MEDIA_UNMOUNTED = "unmounted"; } @@ -17373,8 +18046,14 @@ package android.os { public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable { ctor public ParcelFileDescriptor(android.os.ParcelFileDescriptor); method public static android.os.ParcelFileDescriptor adoptFd(int); + method public boolean canDetectErrors(); + method public void checkError() throws java.io.IOException; method public void close() throws java.io.IOException; + method public void closeWithError(java.lang.String) throws java.io.IOException; method public static android.os.ParcelFileDescriptor[] createPipe() throws java.io.IOException; + method public static android.os.ParcelFileDescriptor[] createReliablePipe() throws java.io.IOException; + method public static android.os.ParcelFileDescriptor[] createReliableSocketPair() throws java.io.IOException; + method public static android.os.ParcelFileDescriptor[] createSocketPair() throws java.io.IOException; method public int describeContents(); method public int detachFd(); method public static android.os.ParcelFileDescriptor dup(java.io.FileDescriptor) throws java.io.IOException; @@ -17386,6 +18065,8 @@ package android.os { method public java.io.FileDescriptor getFileDescriptor(); method public long getStatSize(); method public static android.os.ParcelFileDescriptor open(java.io.File, int) throws java.io.FileNotFoundException; + method public static android.os.ParcelFileDescriptor open(java.io.File, int, android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener) throws java.io.IOException; + method public static int parseMode(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int MODE_APPEND = 33554432; // 0x2000000 @@ -17393,8 +18074,8 @@ package android.os { field public static final int MODE_READ_ONLY = 268435456; // 0x10000000 field public static final int MODE_READ_WRITE = 805306368; // 0x30000000 field public static final int MODE_TRUNCATE = 67108864; // 0x4000000 - field public static final int MODE_WORLD_READABLE = 1; // 0x1 - field public static final int MODE_WORLD_WRITEABLE = 2; // 0x2 + field public static final deprecated int MODE_WORLD_READABLE = 1; // 0x1 + field public static final deprecated int MODE_WORLD_WRITEABLE = 2; // 0x2 field public static final int MODE_WRITE_ONLY = 536870912; // 0x20000000 } @@ -17406,6 +18087,14 @@ package android.os { ctor public ParcelFileDescriptor.AutoCloseOutputStream(android.os.ParcelFileDescriptor); } + public static class ParcelFileDescriptor.FileDescriptorDetachedException extends java.io.IOException { + ctor public ParcelFileDescriptor.FileDescriptorDetachedException(); + } + + public static abstract interface ParcelFileDescriptor.OnCloseListener { + method public abstract void onClose(java.io.IOException); + } + public class ParcelFormatException extends java.lang.RuntimeException { ctor public ParcelFormatException(); ctor public ParcelFormatException(java.lang.String); @@ -17672,6 +18361,7 @@ package android.os { method public boolean isUserAGoat(); method public boolean isUserRunning(android.os.UserHandle); method public boolean isUserRunningOrStopping(android.os.UserHandle); + method public boolean setRestrictionsChallenge(java.lang.String); method public void setUserRestriction(java.lang.String, boolean); method public void setUserRestrictions(android.os.Bundle); method public void setUserRestrictions(android.os.Bundle, android.os.UserHandle); @@ -17858,6 +18548,7 @@ package android.preference { method protected android.view.View onCreateView(android.view.ViewGroup); method public void onDependencyChanged(android.preference.Preference, boolean); method protected java.lang.Object onGetDefaultValue(android.content.res.TypedArray, int); + method public void onParentChanged(android.preference.Preference, boolean); method protected void onPrepareForRemoval(); method protected void onRestoreInstanceState(android.os.Parcelable); method protected android.os.Parcelable onSaveInstanceState(); @@ -17921,6 +18612,7 @@ package android.preference { method public boolean hasHeaders(); method public void invalidateHeaders(); method public boolean isMultiPane(); + method protected boolean isValidFragment(java.lang.String); method public void loadHeadersFromResource(int, java.util.List); method public void onBuildHeaders(java.util.List); method public android.content.Intent onBuildStartFragmentIntent(java.lang.String, android.os.Bundle, int, int); @@ -18096,15 +18788,391 @@ package android.preference { } +package android.print { + + public final class PageRange implements android.os.Parcelable { + ctor public PageRange(int, int); + method public int describeContents(); + method public int getEnd(); + method public int getStart(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.print.PageRange ALL_PAGES; + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class PrintAttributes implements android.os.Parcelable { + method public int describeContents(); + method public int getColorMode(); + method public android.print.PrintAttributes.MediaSize getMediaSize(); + method public android.print.PrintAttributes.Margins getMinMargins(); + method public android.print.PrintAttributes.Resolution getResolution(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int COLOR_MODE_COLOR = 2; // 0x2 + field public static final int COLOR_MODE_MONOCHROME = 1; // 0x1 + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class PrintAttributes.Builder { + ctor public PrintAttributes.Builder(); + method public android.print.PrintAttributes build(); + method public android.print.PrintAttributes.Builder setColorMode(int); + method public android.print.PrintAttributes.Builder setMediaSize(android.print.PrintAttributes.MediaSize); + method public android.print.PrintAttributes.Builder setMinMargins(android.print.PrintAttributes.Margins); + method public android.print.PrintAttributes.Builder setResolution(android.print.PrintAttributes.Resolution); + } + + public static final class PrintAttributes.Margins { + ctor public PrintAttributes.Margins(int, int, int, int); + method public int getBottomMils(); + method public int getLeftMils(); + method public int getRightMils(); + method public int getTopMils(); + field public static final android.print.PrintAttributes.Margins NO_MARGINS; + } + + public static final class PrintAttributes.MediaSize { + ctor public PrintAttributes.MediaSize(java.lang.String, java.lang.String, int, int); + method public android.print.PrintAttributes.MediaSize asLandscape(); + method public android.print.PrintAttributes.MediaSize asPortrait(); + method public int getHeightMils(); + method public java.lang.String getId(); + method public java.lang.String getLabel(android.content.pm.PackageManager); + method public int getWidthMils(); + method public boolean isPortrait(); + field public static final android.print.PrintAttributes.MediaSize ISO_A0; + field public static final android.print.PrintAttributes.MediaSize ISO_A1; + field public static final android.print.PrintAttributes.MediaSize ISO_A10; + field public static final android.print.PrintAttributes.MediaSize ISO_A2; + field public static final android.print.PrintAttributes.MediaSize ISO_A3; + field public static final android.print.PrintAttributes.MediaSize ISO_A4; + field public static final android.print.PrintAttributes.MediaSize ISO_A5; + field public static final android.print.PrintAttributes.MediaSize ISO_A6; + field public static final android.print.PrintAttributes.MediaSize ISO_A7; + field public static final android.print.PrintAttributes.MediaSize ISO_A8; + field public static final android.print.PrintAttributes.MediaSize ISO_A9; + field public static final android.print.PrintAttributes.MediaSize ISO_B0; + field public static final android.print.PrintAttributes.MediaSize ISO_B1; + field public static final android.print.PrintAttributes.MediaSize ISO_B10; + field public static final android.print.PrintAttributes.MediaSize ISO_B2; + field public static final android.print.PrintAttributes.MediaSize ISO_B3; + field public static final android.print.PrintAttributes.MediaSize ISO_B4; + field public static final android.print.PrintAttributes.MediaSize ISO_B5; + field public static final android.print.PrintAttributes.MediaSize ISO_B6; + field public static final android.print.PrintAttributes.MediaSize ISO_B7; + field public static final android.print.PrintAttributes.MediaSize ISO_B8; + field public static final android.print.PrintAttributes.MediaSize ISO_B9; + field public static final android.print.PrintAttributes.MediaSize ISO_C0; + field public static final android.print.PrintAttributes.MediaSize ISO_C1; + field public static final android.print.PrintAttributes.MediaSize ISO_C10; + field public static final android.print.PrintAttributes.MediaSize ISO_C2; + field public static final android.print.PrintAttributes.MediaSize ISO_C3; + field public static final android.print.PrintAttributes.MediaSize ISO_C4; + field public static final android.print.PrintAttributes.MediaSize ISO_C5; + field public static final android.print.PrintAttributes.MediaSize ISO_C6; + field public static final android.print.PrintAttributes.MediaSize ISO_C7; + field public static final android.print.PrintAttributes.MediaSize ISO_C8; + field public static final android.print.PrintAttributes.MediaSize ISO_C9; + field public static final android.print.PrintAttributes.MediaSize JIS_B0; + field public static final android.print.PrintAttributes.MediaSize JIS_B1; + field public static final android.print.PrintAttributes.MediaSize JIS_B10; + field public static final android.print.PrintAttributes.MediaSize JIS_B2; + field public static final android.print.PrintAttributes.MediaSize JIS_B3; + field public static final android.print.PrintAttributes.MediaSize JIS_B4; + field public static final android.print.PrintAttributes.MediaSize JIS_B5; + field public static final android.print.PrintAttributes.MediaSize JIS_B6; + field public static final android.print.PrintAttributes.MediaSize JIS_B7; + field public static final android.print.PrintAttributes.MediaSize JIS_B8; + field public static final android.print.PrintAttributes.MediaSize JIS_B9; + field public static final android.print.PrintAttributes.MediaSize JIS_EXEC; + field public static final android.print.PrintAttributes.MediaSize JPN_CHOU2; + field public static final android.print.PrintAttributes.MediaSize JPN_CHOU3; + field public static final android.print.PrintAttributes.MediaSize JPN_CHOU4; + field public static final android.print.PrintAttributes.MediaSize JPN_HAGAKI; + field public static final android.print.PrintAttributes.MediaSize JPN_KAHU; + field public static final android.print.PrintAttributes.MediaSize JPN_KAKU2; + field public static final android.print.PrintAttributes.MediaSize JPN_OUFUKU; + field public static final android.print.PrintAttributes.MediaSize JPN_YOU4; + field public static final android.print.PrintAttributes.MediaSize NA_FOOLSCAP; + field public static final android.print.PrintAttributes.MediaSize NA_GOVT_LETTER; + field public static final android.print.PrintAttributes.MediaSize NA_INDEX_3X5; + field public static final android.print.PrintAttributes.MediaSize NA_INDEX_4X6; + field public static final android.print.PrintAttributes.MediaSize NA_INDEX_5X8; + field public static final android.print.PrintAttributes.MediaSize NA_JUNIOR_LEGAL; + field public static final android.print.PrintAttributes.MediaSize NA_LEDGER; + field public static final android.print.PrintAttributes.MediaSize NA_LEGAL; + field public static final android.print.PrintAttributes.MediaSize NA_LETTER; + field public static final android.print.PrintAttributes.MediaSize NA_MONARCH; + field public static final android.print.PrintAttributes.MediaSize NA_QUARTO; + field public static final android.print.PrintAttributes.MediaSize NA_TABLOID; + field public static final android.print.PrintAttributes.MediaSize OM_DAI_PA_KAI; + field public static final android.print.PrintAttributes.MediaSize OM_JUURO_KU_KAI; + field public static final android.print.PrintAttributes.MediaSize OM_PA_KAI; + field public static final android.print.PrintAttributes.MediaSize PRC_1; + field public static final android.print.PrintAttributes.MediaSize PRC_10; + field public static final android.print.PrintAttributes.MediaSize PRC_16K; + field public static final android.print.PrintAttributes.MediaSize PRC_2; + field public static final android.print.PrintAttributes.MediaSize PRC_3; + field public static final android.print.PrintAttributes.MediaSize PRC_4; + field public static final android.print.PrintAttributes.MediaSize PRC_5; + field public static final android.print.PrintAttributes.MediaSize PRC_6; + field public static final android.print.PrintAttributes.MediaSize PRC_7; + field public static final android.print.PrintAttributes.MediaSize PRC_8; + field public static final android.print.PrintAttributes.MediaSize PRC_9; + field public static final android.print.PrintAttributes.MediaSize ROC_16K; + field public static final android.print.PrintAttributes.MediaSize ROC_8K; + field public static final android.print.PrintAttributes.MediaSize UNKNOWN_LANDSCAPE; + field public static final android.print.PrintAttributes.MediaSize UNKNOWN_PORTRAIT; + } + + public static final class PrintAttributes.Resolution { + ctor public PrintAttributes.Resolution(java.lang.String, java.lang.String, int, int); + method public int getHorizontalDpi(); + method public java.lang.String getId(); + method public java.lang.String getLabel(); + method public int getVerticalDpi(); + } + + public abstract class PrintDocumentAdapter { + ctor public PrintDocumentAdapter(); + method public void onFinish(); + method public abstract void onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle); + method public void onStart(); + method public abstract void onWrite(android.print.PageRange[], android.os.ParcelFileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback); + field public static final java.lang.String EXTRA_PRINT_PREVIEW = "EXTRA_PRINT_PREVIEW"; + } + + public static abstract class PrintDocumentAdapter.LayoutResultCallback { + method public void onLayoutCancelled(); + method public void onLayoutFailed(java.lang.CharSequence); + method public void onLayoutFinished(android.print.PrintDocumentInfo, boolean); + } + + public static abstract class PrintDocumentAdapter.WriteResultCallback { + method public void onWriteCancelled(); + method public void onWriteFailed(java.lang.CharSequence); + method public void onWriteFinished(android.print.PageRange[]); + } + + public final class PrintDocumentInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getContentType(); + method public long getDataSize(); + method public java.lang.String getName(); + method public int getPageCount(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CONTENT_TYPE_DOCUMENT = 0; // 0x0 + field public static final int CONTENT_TYPE_PHOTO = 1; // 0x1 + field public static final int CONTENT_TYPE_UNKNOWN = -1; // 0xffffffff + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int PAGE_COUNT_UNKNOWN = -1; // 0xffffffff + } + + public static final class PrintDocumentInfo.Builder { + ctor public PrintDocumentInfo.Builder(java.lang.String); + method public android.print.PrintDocumentInfo build(); + method public android.print.PrintDocumentInfo.Builder setContentType(int); + method public android.print.PrintDocumentInfo.Builder setPageCount(int); + } + + public final class PrintJob { + method public void cancel(); + method public android.print.PrintJobId getId(); + method public android.print.PrintJobInfo getInfo(); + method public boolean isBlocked(); + method public boolean isCancelled(); + method public boolean isCompleted(); + method public boolean isFailed(); + method public boolean isQueued(); + method public boolean isStarted(); + method public void restart(); + } + + public final class PrintJobId implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class PrintJobInfo implements android.os.Parcelable { + method public int describeContents(); + method public android.print.PrintAttributes getAttributes(); + method public int getCopies(); + method public long getCreationTime(); + method public android.print.PrintJobId getId(); + method public java.lang.String getLabel(); + method public android.print.PageRange[] getPages(); + method public android.print.PrinterId getPrinterId(); + method public int getState(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int STATE_BLOCKED = 4; // 0x4 + field public static final int STATE_CANCELED = 7; // 0x7 + field public static final int STATE_COMPLETED = 5; // 0x5 + field public static final int STATE_CREATED = 1; // 0x1 + field public static final int STATE_FAILED = 6; // 0x6 + field public static final int STATE_QUEUED = 2; // 0x2 + field public static final int STATE_STARTED = 3; // 0x3 + } + + public static final class PrintJobInfo.Builder { + ctor public PrintJobInfo.Builder(android.print.PrintJobInfo); + method public android.print.PrintJobInfo build(); + method public void putAdvancedOption(java.lang.String, java.lang.String); + method public void putAdvancedOption(java.lang.String, int); + method public void setAttributes(android.print.PrintAttributes); + method public void setCopies(int); + method public void setPages(android.print.PageRange[]); + } + + public final class PrintManager { + method public java.util.List getPrintJobs(); + method public android.print.PrintJob print(java.lang.String, android.print.PrintDocumentAdapter, android.print.PrintAttributes); + } + + public final class PrinterCapabilitiesInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getColorModes(); + method public android.print.PrintAttributes getDefaults(); + method public java.util.List getMediaSizes(); + method public android.print.PrintAttributes.Margins getMinMargins(); + method public java.util.List getResolutions(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class PrinterCapabilitiesInfo.Builder { + ctor public PrinterCapabilitiesInfo.Builder(android.print.PrinterId); + method public android.print.PrinterCapabilitiesInfo.Builder addMediaSize(android.print.PrintAttributes.MediaSize, boolean); + method public android.print.PrinterCapabilitiesInfo.Builder addResolution(android.print.PrintAttributes.Resolution, boolean); + method public android.print.PrinterCapabilitiesInfo build(); + method public android.print.PrinterCapabilitiesInfo.Builder setColorModes(int, int); + method public android.print.PrinterCapabilitiesInfo.Builder setMinMargins(android.print.PrintAttributes.Margins); + } + + public final class PrinterId implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getLocalId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class PrinterInfo implements android.os.Parcelable { + method public int describeContents(); + method public android.print.PrinterCapabilitiesInfo getCapabilities(); + method public java.lang.String getDescription(); + method public android.print.PrinterId getId(); + method public java.lang.String getName(); + method public int getStatus(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int STATUS_BUSY = 2; // 0x2 + field public static final int STATUS_IDLE = 1; // 0x1 + field public static final int STATUS_UNAVAILABLE = 3; // 0x3 + } + + public static final class PrinterInfo.Builder { + ctor public PrinterInfo.Builder(android.print.PrinterId, java.lang.String, int); + ctor public PrinterInfo.Builder(android.print.PrinterInfo); + method public android.print.PrinterInfo build(); + method public android.print.PrinterInfo.Builder setCapabilities(android.print.PrinterCapabilitiesInfo); + method public android.print.PrinterInfo.Builder setDescription(java.lang.String); + method public android.print.PrinterInfo.Builder setName(java.lang.String); + method public android.print.PrinterInfo.Builder setStatus(int); + } + +} + +package android.print.pdf { + + public class PrintedPdfDocument extends android.graphics.pdf.PdfDocument { + ctor public PrintedPdfDocument(android.content.Context, android.print.PrintAttributes); + method public android.graphics.Rect getPageContentRect(); + method public int getPageHeight(); + method public int getPageWidth(); + method public android.graphics.pdf.PdfDocument.Page startPage(int); + } + +} + +package android.printservice { + + public final class PrintDocument { + method public android.os.ParcelFileDescriptor getData(); + method public android.print.PrintDocumentInfo getInfo(); + } + + public final class PrintJob { + method public boolean block(java.lang.String); + method public boolean cancel(); + method public boolean complete(); + method public boolean fail(java.lang.String); + method public int getAdvancedIntOption(java.lang.String); + method public java.lang.String getAdvancedStringOption(java.lang.String); + method public android.printservice.PrintDocument getDocument(); + method public android.print.PrintJobId getId(); + method public android.print.PrintJobInfo getInfo(); + method public java.lang.String getTag(); + method public boolean hasAdvancedOption(java.lang.String); + method public boolean isBlocked(); + method public boolean isCancelled(); + method public boolean isCompleted(); + method public boolean isFailed(); + method public boolean isQueued(); + method public boolean isStarted(); + method public boolean setTag(java.lang.String); + method public boolean start(); + } + + public abstract class PrintService extends android.app.Service { + ctor public PrintService(); + method protected final void attachBaseContext(android.content.Context); + method public final android.print.PrinterId generatePrinterId(java.lang.String); + method public final java.util.List getActivePrintJobs(); + method public final android.os.IBinder onBind(android.content.Intent); + method protected void onConnected(); + method protected abstract android.printservice.PrinterDiscoverySession onCreatePrinterDiscoverySession(); + method protected void onDisconnected(); + method protected abstract void onPrintJobQueued(android.printservice.PrintJob); + method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob); + field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO"; + field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService"; + field public static final java.lang.String SERVICE_META_DATA = "android.printservice"; + } + + public abstract class PrinterDiscoverySession { + ctor public PrinterDiscoverySession(); + method public final void addPrinters(java.util.List); + method public final java.util.List getPrinters(); + method public final java.util.List getTrackedPrinters(); + method public final boolean isDestroyed(); + method public final boolean isPrinterDiscoveryStarted(); + method public abstract void onDestroy(); + method public abstract void onStartPrinterDiscovery(java.util.List); + method public abstract void onStartPrinterStateTracking(android.print.PrinterId); + method public abstract void onStopPrinterDiscovery(); + method public abstract void onStopPrinterStateTracking(android.print.PrinterId); + method public abstract void onValidatePrinters(java.util.List); + method public final void removePrinters(java.util.List); + } + +} + package android.provider { public final class AlarmClock { ctor public AlarmClock(); field public static final java.lang.String ACTION_SET_ALARM = "android.intent.action.SET_ALARM"; + field public static final java.lang.String ACTION_SET_TIMER = "android.intent.action.SET_TIMER"; + field public static final java.lang.String ACTION_SHOW_ALARMS = "android.intent.action.SHOW_ALARMS"; + field public static final java.lang.String EXTRA_DAYS = "android.intent.extra.alarm.DAYS"; field public static final java.lang.String EXTRA_HOUR = "android.intent.extra.alarm.HOUR"; + field public static final java.lang.String EXTRA_LENGTH = "android.intent.extra.alarm.LENGTH"; field public static final java.lang.String EXTRA_MESSAGE = "android.intent.extra.alarm.MESSAGE"; field public static final java.lang.String EXTRA_MINUTES = "android.intent.extra.alarm.MINUTES"; + field public static final java.lang.String EXTRA_RINGTONE = "android.intent.extra.alarm.RINGTONE"; field public static final java.lang.String EXTRA_SKIP_UI = "android.intent.extra.alarm.SKIP_UI"; + field public static final java.lang.String EXTRA_VIBRATE = "android.intent.extra.alarm.VIBRATE"; + field public static final java.lang.String VALUE_RINGTONE_SILENT = "silent"; } public abstract interface BaseColumns { @@ -18474,8 +19542,13 @@ package android.provider { field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; field public static final java.lang.String NUMBER = "number"; + field public static final java.lang.String NUMBER_PRESENTATION = "presentation"; field public static final java.lang.String OFFSET_PARAM_KEY = "offset"; field public static final int OUTGOING_TYPE = 2; // 0x2 + field public static final int PRESENTATION_ALLOWED = 1; // 0x1 + field public static final int PRESENTATION_PAYPHONE = 4; // 0x4 + field public static final int PRESENTATION_RESTRICTED = 2; // 0x2 + field public static final int PRESENTATION_UNKNOWN = 3; // 0x3 field public static final java.lang.String TYPE = "type"; } @@ -19512,6 +20585,81 @@ package android.provider { field public static final android.net.Uri CONTENT_URI; } + public final class DocumentsContract { + method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String); + method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String); + method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String); + method public static android.net.Uri buildRootUri(java.lang.String, java.lang.String); + method public static android.net.Uri buildRootsUri(java.lang.String); + method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String); + method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); + method public static java.lang.String getDocumentId(android.net.Uri); + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal); + method public static java.lang.String getRootId(android.net.Uri); + method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); + method public static boolean isDocumentUri(android.content.Context, android.net.Uri); + field public static final java.lang.String EXTRA_ERROR = "error"; + field public static final java.lang.String EXTRA_INFO = "info"; + field public static final java.lang.String EXTRA_LOADING = "loading"; + field public static final java.lang.String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; + } + + public static final class DocumentsContract.Document { + field public static final java.lang.String COLUMN_DISPLAY_NAME = "_display_name"; + field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id"; + field public static final java.lang.String COLUMN_FLAGS = "flags"; + field public static final java.lang.String COLUMN_ICON = "icon"; + field public static final java.lang.String COLUMN_LAST_MODIFIED = "last_modified"; + field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type"; + field public static final java.lang.String COLUMN_SIZE = "_size"; + field public static final java.lang.String COLUMN_SUMMARY = "summary"; + field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10 + field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 + field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 + field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 + field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 + field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 + field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; + } + + public static final class DocumentsContract.Root { + field public static final java.lang.String COLUMN_AVAILABLE_BYTES = "available_bytes"; + field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id"; + field public static final java.lang.String COLUMN_FLAGS = "flags"; + field public static final java.lang.String COLUMN_ICON = "icon"; + field public static final java.lang.String COLUMN_MIME_TYPES = "mime_types"; + field public static final java.lang.String COLUMN_ROOT_ID = "root_id"; + field public static final java.lang.String COLUMN_SUMMARY = "summary"; + field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final int FLAG_LOCAL_ONLY = 2; // 0x2 + field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 + field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4 + field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8 + } + + public abstract class DocumentsProvider extends android.content.ContentProvider { + ctor public DocumentsProvider(); + method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); + method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException; + method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException; + method public final java.lang.String getType(android.net.Uri); + method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); + method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); + method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); + } + public final deprecated class LiveFolders implements android.provider.BaseColumns { field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER"; field public static final java.lang.String DESCRIPTION = "description"; @@ -19863,6 +21011,7 @@ package android.provider { field public static final java.lang.String ACTION_APPLICATION_DEVELOPMENT_SETTINGS = "android.settings.APPLICATION_DEVELOPMENT_SETTINGS"; field public static final java.lang.String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS"; field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS"; + field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS"; field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS"; field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS"; field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS"; @@ -19878,7 +21027,9 @@ package android.provider { field public static final java.lang.String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS"; field public static final java.lang.String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS"; field public static final java.lang.String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; + field public static final java.lang.String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS"; field public static final java.lang.String ACTION_NFC_SETTINGS = "android.settings.NFC_SETTINGS"; + field public static final java.lang.String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS"; field public static final java.lang.String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS"; field public static final java.lang.String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS"; field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; @@ -19970,12 +21121,12 @@ package android.provider { method public static long getLong(android.content.ContentResolver, java.lang.String) throws android.provider.Settings.SettingNotFoundException; method public static java.lang.String getString(android.content.ContentResolver, java.lang.String); method public static android.net.Uri getUriFor(java.lang.String); - method public static final boolean isLocationProviderEnabled(android.content.ContentResolver, java.lang.String); + method public static final deprecated boolean isLocationProviderEnabled(android.content.ContentResolver, java.lang.String); method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float); method public static boolean putInt(android.content.ContentResolver, java.lang.String, int); method public static boolean putLong(android.content.ContentResolver, java.lang.String, long); method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String); - method public static final void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean); + method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean); field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled"; field public static final java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password"; field public static final deprecated java.lang.String ADB_ENABLED = "adb_enabled"; @@ -19994,7 +21145,12 @@ package android.provider { field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy"; field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility"; field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; - field public static final java.lang.String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; + field public static final java.lang.String LOCATION_MODE = "location_mode"; + field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2 + field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3 + field public static final int LOCATION_MODE_OFF = 0; // 0x0 + field public static final int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1 + field public static final deprecated java.lang.String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; field public static final java.lang.String LOCK_PATTERN_ENABLED = "lock_pattern_autolock"; field public static final deprecated java.lang.String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED = "lock_pattern_tactile_feedback_enabled"; field public static final java.lang.String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern"; @@ -20196,6 +21352,277 @@ package android.provider { method public static void update(android.content.ContentProviderClient, android.net.Uri, byte[]) throws android.os.RemoteException; } + public final class Telephony { + } + + public static abstract interface Telephony.BaseMmsColumns implements android.provider.BaseColumns { + field public static final java.lang.String CONTENT_CLASS = "ct_cls"; + field public static final java.lang.String CONTENT_LOCATION = "ct_l"; + field public static final java.lang.String CONTENT_TYPE = "ct_t"; + field public static final java.lang.String DATE = "date"; + field public static final java.lang.String DATE_SENT = "date_sent"; + field public static final java.lang.String DELIVERY_REPORT = "d_rpt"; + field public static final java.lang.String DELIVERY_TIME = "d_tm"; + field public static final java.lang.String EXPIRY = "exp"; + field public static final java.lang.String LOCKED = "locked"; + field public static final java.lang.String MESSAGE_BOX = "msg_box"; + field public static final int MESSAGE_BOX_ALL = 0; // 0x0 + field public static final int MESSAGE_BOX_DRAFTS = 3; // 0x3 + field public static final int MESSAGE_BOX_INBOX = 1; // 0x1 + field public static final int MESSAGE_BOX_OUTBOX = 4; // 0x4 + field public static final int MESSAGE_BOX_SENT = 2; // 0x2 + field public static final java.lang.String MESSAGE_CLASS = "m_cls"; + field public static final java.lang.String MESSAGE_ID = "m_id"; + field public static final java.lang.String MESSAGE_SIZE = "m_size"; + field public static final java.lang.String MESSAGE_TYPE = "m_type"; + field public static final java.lang.String MMS_VERSION = "v"; + field public static final java.lang.String PRIORITY = "pri"; + field public static final java.lang.String READ = "read"; + field public static final java.lang.String READ_REPORT = "rr"; + field public static final java.lang.String READ_STATUS = "read_status"; + field public static final java.lang.String REPORT_ALLOWED = "rpt_a"; + field public static final java.lang.String RESPONSE_STATUS = "resp_st"; + field public static final java.lang.String RESPONSE_TEXT = "resp_txt"; + field public static final java.lang.String RETRIEVE_STATUS = "retr_st"; + field public static final java.lang.String RETRIEVE_TEXT = "retr_txt"; + field public static final java.lang.String RETRIEVE_TEXT_CHARSET = "retr_txt_cs"; + field public static final java.lang.String SEEN = "seen"; + field public static final java.lang.String STATUS = "st"; + field public static final java.lang.String SUBJECT = "sub"; + field public static final java.lang.String SUBJECT_CHARSET = "sub_cs"; + field public static final java.lang.String TEXT_ONLY = "text_only"; + field public static final java.lang.String THREAD_ID = "thread_id"; + field public static final java.lang.String TRANSACTION_ID = "tr_id"; + } + + public static abstract interface Telephony.CanonicalAddressesColumns implements android.provider.BaseColumns { + field public static final java.lang.String ADDRESS = "address"; + } + + public static final class Telephony.Carriers implements android.provider.BaseColumns { + field public static final java.lang.String APN = "apn"; + field public static final java.lang.String AUTH_TYPE = "authtype"; + field public static final java.lang.String BEARER = "bearer"; + field public static final java.lang.String CARRIER_ENABLED = "carrier_enabled"; + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String CURRENT = "current"; + field public static final java.lang.String DEFAULT_SORT_ORDER = "name ASC"; + field public static final java.lang.String MCC = "mcc"; + field public static final java.lang.String MMSC = "mmsc"; + field public static final java.lang.String MMSPORT = "mmsport"; + field public static final java.lang.String MMSPROXY = "mmsproxy"; + field public static final java.lang.String MNC = "mnc"; + field public static final java.lang.String MVNO_MATCH_DATA = "mvno_match_data"; + field public static final java.lang.String MVNO_TYPE = "mvno_type"; + field public static final java.lang.String NAME = "name"; + field public static final java.lang.String NUMERIC = "numeric"; + field public static final java.lang.String PASSWORD = "password"; + field public static final java.lang.String PORT = "port"; + field public static final java.lang.String PROTOCOL = "protocol"; + field public static final java.lang.String PROXY = "proxy"; + field public static final java.lang.String ROAMING_PROTOCOL = "roaming_protocol"; + field public static final java.lang.String SERVER = "server"; + field public static final java.lang.String TYPE = "type"; + field public static final java.lang.String USER = "user"; + } + + public static final class Telephony.Mms implements android.provider.Telephony.BaseMmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + field public static final android.net.Uri REPORT_REQUEST_URI; + field public static final android.net.Uri REPORT_STATUS_URI; + } + + public static final class Telephony.Mms.Addr implements android.provider.BaseColumns { + field public static final java.lang.String ADDRESS = "address"; + field public static final java.lang.String CHARSET = "charset"; + field public static final java.lang.String CONTACT_ID = "contact_id"; + field public static final java.lang.String MSG_ID = "msg_id"; + field public static final java.lang.String TYPE = "type"; + } + + public static final class Telephony.Mms.Draft implements android.provider.Telephony.BaseMmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.Mms.Inbox implements android.provider.Telephony.BaseMmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.Mms.Intents { + field public static final java.lang.String CONTENT_CHANGED_ACTION = "android.intent.action.CONTENT_CHANGED"; + field public static final java.lang.String DELETED_CONTENTS = "deleted_contents"; + } + + public static final class Telephony.Mms.Outbox implements android.provider.Telephony.BaseMmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.Mms.Part implements android.provider.BaseColumns { + field public static final java.lang.String CHARSET = "chset"; + field public static final java.lang.String CONTENT_DISPOSITION = "cd"; + field public static final java.lang.String CONTENT_ID = "cid"; + field public static final java.lang.String CONTENT_LOCATION = "cl"; + field public static final java.lang.String CONTENT_TYPE = "ct"; + field public static final java.lang.String CT_START = "ctt_s"; + field public static final java.lang.String CT_TYPE = "ctt_t"; + field public static final java.lang.String FILENAME = "fn"; + field public static final java.lang.String MSG_ID = "mid"; + field public static final java.lang.String NAME = "name"; + field public static final java.lang.String SEQ = "seq"; + field public static final java.lang.String TEXT = "text"; + field public static final java.lang.String _DATA = "_data"; + } + + public static final class Telephony.Mms.Rate { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String SENT_TIME = "sent_time"; + } + + public static final class Telephony.Mms.Sent implements android.provider.Telephony.BaseMmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.MmsSms implements android.provider.BaseColumns { + field public static final android.net.Uri CONTENT_CONVERSATIONS_URI; + field public static final android.net.Uri CONTENT_DRAFT_URI; + field public static final android.net.Uri CONTENT_FILTER_BYPHONE_URI; + field public static final android.net.Uri CONTENT_LOCKED_URI; + field public static final android.net.Uri CONTENT_UNDELIVERED_URI; + field public static final android.net.Uri CONTENT_URI; + field public static final int ERR_TYPE_GENERIC = 1; // 0x1 + field public static final int ERR_TYPE_GENERIC_PERMANENT = 10; // 0xa + field public static final int ERR_TYPE_MMS_PROTO_PERMANENT = 12; // 0xc + field public static final int ERR_TYPE_MMS_PROTO_TRANSIENT = 3; // 0x3 + field public static final int ERR_TYPE_SMS_PROTO_PERMANENT = 11; // 0xb + field public static final int ERR_TYPE_SMS_PROTO_TRANSIENT = 2; // 0x2 + field public static final int ERR_TYPE_TRANSPORT_FAILURE = 4; // 0x4 + field public static final int MMS_PROTO = 1; // 0x1 + field public static final int NO_ERROR = 0; // 0x0 + field public static final android.net.Uri SEARCH_URI; + field public static final int SMS_PROTO = 0; // 0x0 + field public static final java.lang.String TYPE_DISCRIMINATOR_COLUMN = "transport_type"; + } + + public static final class Telephony.MmsSms.PendingMessages implements android.provider.BaseColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DUE_TIME = "due_time"; + field public static final java.lang.String ERROR_CODE = "err_code"; + field public static final java.lang.String ERROR_TYPE = "err_type"; + field public static final java.lang.String LAST_TRY = "last_try"; + field public static final java.lang.String MSG_ID = "msg_id"; + field public static final java.lang.String MSG_TYPE = "msg_type"; + field public static final java.lang.String PROTO_TYPE = "proto_type"; + field public static final java.lang.String RETRY_INDEX = "retry_index"; + } + + public static final class Telephony.Sms implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns { + method public static java.lang.String getDefaultSmsPackage(android.content.Context); + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.Sms.Conversations implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + field public static final java.lang.String MESSAGE_COUNT = "msg_count"; + field public static final java.lang.String SNIPPET = "snippet"; + } + + public static final class Telephony.Sms.Draft implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.Sms.Inbox implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.Sms.Intents { + method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent); + field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT"; + field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED"; + field public static final java.lang.String EXTRA_PACKAGE_NAME = "package"; + field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5 + field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2 + field public static final int RESULT_SMS_HANDLED = 1; // 0x1 + field public static final int RESULT_SMS_OUT_OF_MEMORY = 3; // 0x3 + field public static final int RESULT_SMS_UNSUPPORTED = 4; // 0x4 + field public static final java.lang.String SIM_FULL_ACTION = "android.provider.Telephony.SIM_FULL"; + field public static final java.lang.String SMS_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_CB_RECEIVED"; + field public static final java.lang.String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER"; + field public static final java.lang.String SMS_EMERGENCY_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; + field public static final java.lang.String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED"; + field public static final java.lang.String SMS_REJECTED_ACTION = "android.provider.Telephony.SMS_REJECTED"; + field public static final java.lang.String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION = "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED"; + field public static final java.lang.String WAP_PUSH_DELIVER_ACTION = "android.provider.Telephony.WAP_PUSH_DELIVER"; + field public static final java.lang.String WAP_PUSH_RECEIVED_ACTION = "android.provider.Telephony.WAP_PUSH_RECEIVED"; + } + + public static final class Telephony.Sms.Outbox implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static final class Telephony.Sms.Sent implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; + } + + public static abstract interface Telephony.TextBasedSmsColumns { + field public static final java.lang.String ADDRESS = "address"; + field public static final java.lang.String BODY = "body"; + field public static final java.lang.String DATE = "date"; + field public static final java.lang.String DATE_SENT = "date_sent"; + field public static final java.lang.String ERROR_CODE = "error_code"; + field public static final java.lang.String LOCKED = "locked"; + field public static final int MESSAGE_TYPE_ALL = 0; // 0x0 + field public static final int MESSAGE_TYPE_DRAFT = 3; // 0x3 + field public static final int MESSAGE_TYPE_FAILED = 5; // 0x5 + field public static final int MESSAGE_TYPE_INBOX = 1; // 0x1 + field public static final int MESSAGE_TYPE_OUTBOX = 4; // 0x4 + field public static final int MESSAGE_TYPE_QUEUED = 6; // 0x6 + field public static final int MESSAGE_TYPE_SENT = 2; // 0x2 + field public static final java.lang.String PERSON = "person"; + field public static final java.lang.String PROTOCOL = "protocol"; + field public static final java.lang.String READ = "read"; + field public static final java.lang.String REPLY_PATH_PRESENT = "reply_path_present"; + field public static final java.lang.String SEEN = "seen"; + field public static final java.lang.String SERVICE_CENTER = "service_center"; + field public static final java.lang.String STATUS = "status"; + field public static final int STATUS_COMPLETE = 0; // 0x0 + field public static final int STATUS_FAILED = 64; // 0x40 + field public static final int STATUS_NONE = -1; // 0xffffffff + field public static final int STATUS_PENDING = 32; // 0x20 + field public static final java.lang.String SUBJECT = "subject"; + field public static final java.lang.String THREAD_ID = "thread_id"; + field public static final java.lang.String TYPE = "type"; + } + + public static final class Telephony.Threads implements android.provider.Telephony.ThreadsColumns { + field public static final int BROADCAST_THREAD = 1; // 0x1 + field public static final int COMMON_THREAD = 0; // 0x0 + field public static final android.net.Uri CONTENT_URI; + field public static final android.net.Uri OBSOLETE_THREADS_URI; + } + + public static abstract interface Telephony.ThreadsColumns implements android.provider.BaseColumns { + field public static final java.lang.String DATE = "date"; + field public static final java.lang.String ERROR = "error"; + field public static final java.lang.String HAS_ATTACHMENT = "has_attachment"; + field public static final java.lang.String MESSAGE_COUNT = "message_count"; + field public static final java.lang.String READ = "read"; + field public static final java.lang.String RECIPIENT_IDS = "recipient_ids"; + field public static final java.lang.String SNIPPET = "snippet"; + field public static final java.lang.String SNIPPET_CHARSET = "snippet_cs"; + field public static final java.lang.String TYPE = "type"; + } + public class UserDictionary { ctor public UserDictionary(); field public static final java.lang.String AUTHORITY = "user_dictionary"; @@ -20325,6 +21752,7 @@ package android.renderscript { method public deprecated synchronized void resize(int); method public void setFromFieldPacker(int, android.renderscript.FieldPacker); method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker); + method public void setOnBufferAvailableListener(android.renderscript.Allocation.OnBufferAvailableListener); method public void setSurface(android.view.Surface); method public void syncAll(int); field public static final int USAGE_GRAPHICS_CONSTANTS = 8; // 0x8 @@ -20345,6 +21773,10 @@ package android.renderscript { enum_constant public static final android.renderscript.Allocation.MipmapControl MIPMAP_ON_SYNC_TO_TEXTURE; } + public static abstract interface Allocation.OnBufferAvailableListener { + method public abstract void onBufferAvailable(android.renderscript.Allocation); + } + public class AllocationAdapter extends android.renderscript.Allocation { method public static android.renderscript.AllocationAdapter create1D(android.renderscript.RenderScript, android.renderscript.Allocation); method public static android.renderscript.AllocationAdapter create2D(android.renderscript.RenderScript, android.renderscript.Allocation); @@ -20471,6 +21903,7 @@ package android.renderscript { method public static android.renderscript.Element U8_2(android.renderscript.RenderScript); method public static android.renderscript.Element U8_3(android.renderscript.RenderScript); method public static android.renderscript.Element U8_4(android.renderscript.RenderScript); + method public static android.renderscript.Element YUV(android.renderscript.RenderScript); method public static android.renderscript.Element createPixel(android.renderscript.RenderScript, android.renderscript.Element.DataType, android.renderscript.Element.DataKind); method public static android.renderscript.Element createVector(android.renderscript.RenderScript, android.renderscript.Element.DataType, int); method public int getBytesSize(); @@ -20994,9 +22427,12 @@ package android.renderscript { } public final class ScriptIntrinsicColorMatrix extends android.renderscript.ScriptIntrinsic { - method public static android.renderscript.ScriptIntrinsicColorMatrix create(android.renderscript.RenderScript, android.renderscript.Element); + method public static deprecated android.renderscript.ScriptIntrinsicColorMatrix create(android.renderscript.RenderScript, android.renderscript.Element); + method public static android.renderscript.ScriptIntrinsicColorMatrix create(android.renderscript.RenderScript); method public void forEach(android.renderscript.Allocation, android.renderscript.Allocation); method public android.renderscript.Script.KernelID getKernelID(); + method public void setAdd(android.renderscript.Float4); + method public void setAdd(float, float, float, float); method public void setColorMatrix(android.renderscript.Matrix4f); method public void setColorMatrix(android.renderscript.Matrix3f); method public void setGreyscale(); @@ -21022,6 +22458,16 @@ package android.renderscript { method public void setInput(android.renderscript.Allocation); } + public final class ScriptIntrinsicHistogram extends android.renderscript.ScriptIntrinsic { + method public static android.renderscript.ScriptIntrinsicHistogram create(android.renderscript.RenderScript, android.renderscript.Element); + method public void forEach(android.renderscript.Allocation); + method public void forEach_Dot(android.renderscript.Allocation); + method public android.renderscript.Script.FieldID getFieldID_Input(); + method public android.renderscript.Script.KernelID getKernelID_Separate(); + method public void setDotCoefficients(float, float, float, float); + method public void setOutput(android.renderscript.Allocation); + } + public final class ScriptIntrinsicLUT extends android.renderscript.ScriptIntrinsic { method public static android.renderscript.ScriptIntrinsicLUT create(android.renderscript.RenderScript, android.renderscript.Element); method public void forEach(android.renderscript.Allocation, android.renderscript.Allocation); @@ -21170,8 +22616,11 @@ package android.security { } public final class KeyPairGeneratorSpec implements java.security.spec.AlgorithmParameterSpec { + method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec(); method public android.content.Context getContext(); method public java.util.Date getEndDate(); + method public int getKeySize(); + method public java.lang.String getKeyType(); method public java.lang.String getKeystoreAlias(); method public java.math.BigInteger getSerialNumber(); method public java.util.Date getStartDate(); @@ -21182,9 +22631,12 @@ package android.security { public static final class KeyPairGeneratorSpec.Builder { ctor public KeyPairGeneratorSpec.Builder(android.content.Context); method public android.security.KeyPairGeneratorSpec build(); + method public android.security.KeyPairGeneratorSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec); method public android.security.KeyPairGeneratorSpec.Builder setAlias(java.lang.String); method public android.security.KeyPairGeneratorSpec.Builder setEncryptionRequired(); method public android.security.KeyPairGeneratorSpec.Builder setEndDate(java.util.Date); + method public android.security.KeyPairGeneratorSpec.Builder setKeySize(int); + method public android.security.KeyPairGeneratorSpec.Builder setKeyType(java.lang.String) throws java.security.NoSuchAlgorithmException; method public android.security.KeyPairGeneratorSpec.Builder setSerialNumber(java.math.BigInteger); method public android.security.KeyPairGeneratorSpec.Builder setStartDate(java.util.Date); method public android.security.KeyPairGeneratorSpec.Builder setSubject(javax.security.auth.x500.X500Principal); @@ -21456,6 +22908,7 @@ package android.speech.tts { public final class SynthesisRequest { ctor public SynthesisRequest(java.lang.String, android.os.Bundle); + method public int getCallerUid(); method public java.lang.String getCountry(); method public java.lang.String getLanguage(); method public android.os.Bundle getParams(); @@ -21936,6 +23389,8 @@ package android.telephony { method public java.lang.String getDeviceSoftwareVersion(); method public java.lang.String getGroupIdLevel1(); method public java.lang.String getLine1Number(); + method public java.lang.String getMmsUAProfUrl(); + method public java.lang.String getMmsUserAgent(); method public java.util.List getNeighboringCellInfo(); method public java.lang.String getNetworkCountryIso(); method public java.lang.String getNetworkOperator(); @@ -22475,11 +23930,14 @@ package android.test.mock { method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); + method public java.io.File[] getExternalCacheDirs(); method public java.io.File getExternalFilesDir(java.lang.String); + method public java.io.File[] getExternalFilesDirs(java.lang.String); method public java.io.File getFileStreamPath(java.lang.String); method public java.io.File getFilesDir(); method public android.os.Looper getMainLooper(); method public java.io.File getObbDir(); + method public java.io.File[] getObbDirs(); method public java.lang.String getPackageCodePath(); method public android.content.pm.PackageManager getPackageManager(); method public java.lang.String getPackageName(); @@ -22546,6 +24004,7 @@ package android.test.mock { method public float getFloat(int); method public int getInt(int); method public long getLong(int); + method public android.net.Uri getNotificationUri(); method public int getPosition(); method public short getShort(int); method public java.lang.String getString(int); @@ -22638,6 +24097,7 @@ package android.test.mock { method public java.util.List queryInstrumentation(java.lang.String, int); method public java.util.List queryIntentActivities(android.content.Intent, int); method public java.util.List queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int); + method public java.util.List queryIntentContentProviders(android.content.Intent, int); method public java.util.List queryIntentServices(android.content.Intent, int); method public java.util.List queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public void removePackageFromPreferred(java.lang.String); @@ -23539,7 +24999,9 @@ package android.text.method { method public static void clearMetaKeyState(android.text.Editable, int); method public long clearMetaKeyState(long, int); method public static final int getMetaState(java.lang.CharSequence); + method public static final int getMetaState(java.lang.CharSequence, android.view.KeyEvent); method public static final int getMetaState(java.lang.CharSequence, int); + method public static final int getMetaState(java.lang.CharSequence, int, android.view.KeyEvent); method public static final int getMetaState(long); method public static final int getMetaState(long, int); method public static long handleKeyDown(long, int, android.view.KeyEvent); @@ -24089,6 +25551,125 @@ package android.text.util { } +package android.transition { + + public class AutoTransition extends android.transition.TransitionSet { + ctor public AutoTransition(); + } + + public class ChangeBounds extends android.transition.Transition { + ctor public ChangeBounds(); + method public void captureEndValues(android.transition.TransitionValues); + method public void captureStartValues(android.transition.TransitionValues); + method public void setReparent(boolean); + method public void setResizeClip(boolean); + } + + public class Fade extends android.transition.Visibility { + ctor public Fade(); + ctor public Fade(int); + field public static final int IN = 1; // 0x1 + field public static final int OUT = 2; // 0x2 + } + + public final class Scene { + ctor public Scene(android.view.ViewGroup); + ctor public Scene(android.view.ViewGroup, android.view.ViewGroup); + method public void enter(); + method public void exit(); + method public static android.transition.Scene getSceneForLayout(android.view.ViewGroup, int, android.content.Context); + method public android.view.ViewGroup getSceneRoot(); + method public void setEnterAction(java.lang.Runnable); + method public void setExitAction(java.lang.Runnable); + } + + public abstract class Transition implements java.lang.Cloneable { + ctor public Transition(); + method public android.transition.Transition addListener(android.transition.Transition.TransitionListener); + method public android.transition.Transition addTarget(int); + method public android.transition.Transition addTarget(android.view.View); + method public abstract void captureEndValues(android.transition.TransitionValues); + method public abstract void captureStartValues(android.transition.TransitionValues); + method public android.transition.Transition clone(); + method public android.animation.Animator createAnimator(android.view.ViewGroup, android.transition.TransitionValues, android.transition.TransitionValues); + method public android.transition.Transition excludeChildren(int, boolean); + method public android.transition.Transition excludeChildren(android.view.View, boolean); + method public android.transition.Transition excludeChildren(java.lang.Class, boolean); + method public android.transition.Transition excludeTarget(int, boolean); + method public android.transition.Transition excludeTarget(android.view.View, boolean); + method public android.transition.Transition excludeTarget(java.lang.Class, boolean); + method public long getDuration(); + method public android.animation.TimeInterpolator getInterpolator(); + method public java.lang.String getName(); + method public long getStartDelay(); + method public java.util.List getTargetIds(); + method public java.util.List getTargets(); + method public java.lang.String[] getTransitionProperties(); + method public android.transition.TransitionValues getTransitionValues(android.view.View, boolean); + method public android.transition.Transition removeListener(android.transition.Transition.TransitionListener); + method public android.transition.Transition removeTarget(int); + method public android.transition.Transition removeTarget(android.view.View); + method public android.transition.Transition setDuration(long); + method public android.transition.Transition setInterpolator(android.animation.TimeInterpolator); + method public android.transition.Transition setStartDelay(long); + } + + public static abstract interface Transition.TransitionListener { + method public abstract void onTransitionCancel(android.transition.Transition); + method public abstract void onTransitionEnd(android.transition.Transition); + method public abstract void onTransitionPause(android.transition.Transition); + method public abstract void onTransitionResume(android.transition.Transition); + method public abstract void onTransitionStart(android.transition.Transition); + } + + public class TransitionInflater { + method public static android.transition.TransitionInflater from(android.content.Context); + method public android.transition.Transition inflateTransition(int); + method public android.transition.TransitionManager inflateTransitionManager(int, android.view.ViewGroup); + } + + public class TransitionManager { + ctor public TransitionManager(); + method public static void beginDelayedTransition(android.view.ViewGroup); + method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition); + method public static android.transition.Transition getDefaultTransition(); + method public static void go(android.transition.Scene); + method public static void go(android.transition.Scene, android.transition.Transition); + method public void setDefaultTransition(android.transition.Transition); + method public void setTransition(android.transition.Scene, android.transition.Transition); + method public void setTransition(android.transition.Scene, android.transition.Scene, android.transition.Transition); + method public void transitionTo(android.transition.Scene); + } + + public class TransitionSet extends android.transition.Transition { + ctor public TransitionSet(); + method public android.transition.TransitionSet addTransition(android.transition.Transition); + method public void captureEndValues(android.transition.TransitionValues); + method public void captureStartValues(android.transition.TransitionValues); + method public int getOrdering(); + method public android.transition.TransitionSet removeTransition(android.transition.Transition); + method public android.transition.TransitionSet setOrdering(int); + field public static final int ORDERING_SEQUENTIAL = 1; // 0x1 + field public static final int ORDERING_TOGETHER = 0; // 0x0 + } + + public class TransitionValues { + ctor public TransitionValues(); + field public final java.util.Map values; + field public android.view.View view; + } + + public abstract class Visibility extends android.transition.Transition { + ctor public Visibility(); + method public void captureEndValues(android.transition.TransitionValues); + method public void captureStartValues(android.transition.TransitionValues); + method public boolean isVisible(android.transition.TransitionValues); + method public android.animation.Animator onAppear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int); + method public android.animation.Animator onDisappear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int); + } + +} + package android.util { public class AndroidException extends java.lang.Exception { @@ -24105,6 +25686,33 @@ package android.util { ctor public AndroidRuntimeException(java.lang.Exception); } + public final class ArrayMap implements java.util.Map { + ctor public ArrayMap(); + ctor public ArrayMap(int); + ctor public ArrayMap(android.util.ArrayMap); + method public void clear(); + method public boolean containsAll(java.util.Collection); + method public boolean containsKey(java.lang.Object); + method public boolean containsValue(java.lang.Object); + method public void ensureCapacity(int); + method public java.util.Set> entrySet(); + method public V get(java.lang.Object); + method public boolean isEmpty(); + method public K keyAt(int); + method public java.util.Set keySet(); + method public V put(K, V); + method public void putAll(android.util.ArrayMap); + method public void putAll(java.util.Map); + method public V remove(java.lang.Object); + method public boolean removeAll(java.util.Collection); + method public V removeAt(int); + method public boolean retainAll(java.util.Collection); + method public V setValueAt(int, V); + method public int size(); + method public V valueAt(int); + method public java.util.Collection values(); + } + public class AtomicFile { ctor public AtomicFile(java.io.File); method public void delete(); @@ -24300,6 +25908,13 @@ package android.util { method public android.util.JsonWriter value(java.lang.Number) throws java.io.IOException; } + public final class LayoutDirection { + field public static final int INHERIT = 2; // 0x2 + field public static final int LOCALE = 3; // 0x3 + field public static final int LTR = 0; // 0x0 + field public static final int RTL = 1; // 0x1 + } + public final class Log { method public static int d(java.lang.String, java.lang.String); method public static int d(java.lang.String, java.lang.String, java.lang.Throwable); @@ -24458,6 +26073,7 @@ package android.util { method public void put(int, E); method public void remove(int); method public void removeAt(int); + method public void removeAtRange(int, int); method public void setValueAt(int, E); method public int size(); method public E valueAt(int); @@ -24741,6 +26357,8 @@ package android.view { method public deprecated int getWidth(); method public boolean isValid(); field public static final int DEFAULT_DISPLAY = 0; // 0x0 + field public static final int FLAG_PRESENTATION = 8; // 0x8 + field public static final int FLAG_PRIVATE = 4; // 0x4 field public static final int FLAG_SECURE = 2; // 0x2 field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1 } @@ -24868,6 +26486,7 @@ package android.view { public final class InputDevice implements android.os.Parcelable { method public int describeContents(); + method public int getControllerNumber(); method public java.lang.String getDescriptor(); method public static android.view.InputDevice getDevice(int); method public static int[] getDeviceIds(); @@ -24878,8 +26497,11 @@ package android.view { method public android.view.InputDevice.MotionRange getMotionRange(int, int); method public java.util.List getMotionRanges(); method public java.lang.String getName(); + method public int getProductId(); method public int getSources(); + method public int getVendorId(); method public android.os.Vibrator getVibrator(); + method public boolean[] hasKeys(int...); method public boolean isVirtual(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -25195,6 +26817,7 @@ package android.view { field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47 field public static final int KEYCODE_M = 41; // 0x29 field public static final int KEYCODE_MANNER_MODE = 205; // 0xcd + field public static final int KEYCODE_MEDIA_AUDIO_TRACK = 222; // 0xde field public static final int KEYCODE_MEDIA_CLOSE = 128; // 0x80 field public static final int KEYCODE_MEDIA_EJECT = 129; // 0x81 field public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; // 0x5a @@ -25467,6 +27090,7 @@ package android.view { } public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable { + method public static java.lang.String actionToString(int); method public final void addBatch(long, float, float, float, float, int); method public final void addBatch(long, android.view.MotionEvent.PointerCoords[], int); method public static int axisFromString(java.lang.String); @@ -25683,6 +27307,7 @@ package android.view { public class ScaleGestureDetector { ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener); + ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener, android.os.Handler); method public float getCurrentSpan(); method public float getCurrentSpanX(); method public float getCurrentSpanY(); @@ -25695,7 +27320,9 @@ package android.view { method public float getScaleFactor(); method public long getTimeDelta(); method public boolean isInProgress(); + method public boolean isQuickScaleEnabled(); method public boolean onTouchEvent(android.view.MotionEvent); + method public void setQuickScaleEnabled(boolean); } public static abstract interface ScaleGestureDetector.OnScaleGestureListener { @@ -25749,7 +27376,7 @@ package android.view { field public static final int ROTATION_90 = 1; // 0x1 } - public static class Surface.OutOfResourcesException extends java.lang.Exception { + public static class Surface.OutOfResourcesException extends java.lang.RuntimeException { ctor public Surface.OutOfResourcesException(); ctor public Surface.OutOfResourcesException(java.lang.String); } @@ -25871,9 +27498,13 @@ package android.view { method public void buildDrawingCache(boolean); method public void buildLayer(); method public boolean callOnClick(); + method public boolean canResolveLayoutDirection(); + method public boolean canResolveTextAlignment(); + method public boolean canResolveTextDirection(); method public boolean canScrollHorizontally(int); method public boolean canScrollVertically(int); method public void cancelLongPress(); + method public final void cancelPendingInputEvents(); method public boolean checkInputConnectionProxy(android.view.View); method public void clearAnimation(); method public void clearFocus(); @@ -25923,6 +27554,7 @@ package android.view { method public android.view.View focusSearch(int); method public void forceLayout(); method public static int generateViewId(); + method public int getAccessibilityLiveRegion(); method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider(); method public float getAlpha(); method public android.view.animation.Animation getAnimation(); @@ -26056,6 +27688,7 @@ package android.view { method public void invalidate(); method public void invalidateDrawable(android.graphics.drawable.Drawable); method public boolean isActivated(); + method public boolean isAttachedToWindow(); method public boolean isClickable(); method public boolean isDirty(); method public boolean isDrawingCacheEnabled(); @@ -26072,6 +27705,8 @@ package android.view { method public boolean isInEditMode(); method public boolean isInLayout(); method public boolean isInTouchMode(); + method public boolean isLaidOut(); + method public boolean isLayoutDirectionResolved(); method public boolean isLayoutRequested(); method public boolean isLongClickable(); method public boolean isOpaque(); @@ -26085,6 +27720,8 @@ package android.view { method public boolean isSelected(); method public boolean isShown(); method public boolean isSoundEffectsEnabled(); + method public boolean isTextAlignmentResolved(); + method public boolean isTextDirectionResolved(); method public boolean isVerticalFadingEdgeEnabled(); method public boolean isVerticalScrollBarEnabled(); method public void jumpDrawablesToCurrentState(); @@ -26096,6 +27733,7 @@ package android.view { method protected void onAnimationEnd(); method protected void onAnimationStart(); method protected void onAttachedToWindow(); + method public void onCancelPendingInputEvents(); method public boolean onCheckIsTextEditor(); method protected void onConfigurationChanged(android.content.res.Configuration); method protected void onCreateContextMenu(android.view.ContextMenu); @@ -26178,6 +27816,7 @@ package android.view { method public void sendAccessibilityEvent(int); method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent); method public void setAccessibilityDelegate(android.view.View.AccessibilityDelegate); + method public void setAccessibilityLiveRegion(int); method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); @@ -26283,6 +27922,9 @@ package android.view { method protected boolean verifyDrawable(android.graphics.drawable.Drawable); method public boolean willNotCacheDrawing(); method public boolean willNotDraw(); + field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2 + field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 + field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property ALPHA; field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0 field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000 @@ -26314,6 +27956,7 @@ package android.view { field public static final int HAPTIC_FEEDBACK_ENABLED = 268435456; // 0x10000000 field public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0; // 0x0 field public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 2; // 0x2 + field public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 4; // 0x4 field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1 field public static final int INVISIBLE = 4; // 0x4 field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000 @@ -26346,6 +27989,7 @@ package android.view { field protected static final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET; field protected static final int[] PRESSED_SELECTED_STATE_SET; field protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET; + field protected static final int[] PRESSED_STATE_SET; field protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET; field public static final android.util.Property ROTATION; field public static final android.util.Property ROTATION_X; @@ -26368,6 +28012,8 @@ package android.view { field public static final deprecated int STATUS_BAR_VISIBLE = 0; // 0x0 field public static final int SYSTEM_UI_FLAG_FULLSCREEN = 4; // 0x4 field public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2; // 0x2 + field public static final int SYSTEM_UI_FLAG_IMMERSIVE = 2048; // 0x800 + field public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 4096; // 0x1000 field public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 1024; // 0x400 field public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 512; // 0x200 field public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 256; // 0x100 @@ -26588,6 +28234,7 @@ package android.view { method protected boolean canAnimate(); method protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams); method public void childDrawableStateChanged(android.view.View); + method public void childHasTransientStateChanged(android.view.View, boolean); method protected void cleanupLayoutState(android.view.View); method public void clearChildFocus(android.view.View); method public void clearDisappearingChildren(); @@ -26634,6 +28281,7 @@ package android.view { method protected void measureChild(android.view.View, int, int); method protected void measureChildWithMargins(android.view.View, int, int, int, int); method protected void measureChildren(int, int); + method public void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect); method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect); method public boolean onInterceptHoverEvent(android.view.MotionEvent); @@ -26748,17 +28396,28 @@ package android.view { public abstract interface ViewParent { method public abstract void bringChildToFront(android.view.View); + method public abstract boolean canResolveLayoutDirection(); + method public abstract boolean canResolveTextAlignment(); + method public abstract boolean canResolveTextDirection(); method public abstract void childDrawableStateChanged(android.view.View); + method public abstract void childHasTransientStateChanged(android.view.View, boolean); method public abstract void clearChildFocus(android.view.View); method public abstract void createContextMenu(android.view.ContextMenu); method public abstract android.view.View focusSearch(android.view.View, int); method public abstract void focusableViewAvailable(android.view.View); method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point); + method public abstract int getLayoutDirection(); method public abstract android.view.ViewParent getParent(); method public abstract android.view.ViewParent getParentForAccessibility(); + method public abstract int getTextAlignment(); + method public abstract int getTextDirection(); method public abstract void invalidateChild(android.view.View, android.graphics.Rect); method public abstract android.view.ViewParent invalidateChildInParent(int[], android.graphics.Rect); + method public abstract boolean isLayoutDirectionResolved(); method public abstract boolean isLayoutRequested(); + method public abstract boolean isTextAlignmentResolved(); + method public abstract boolean isTextDirectionResolved(); + method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract void recomputeViewAttributes(android.view.View); method public abstract void requestChildFocus(android.view.View, android.view.View); method public abstract boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean); @@ -26792,6 +28451,7 @@ package android.view { method public android.view.ViewPropertyAnimator setInterpolator(android.animation.TimeInterpolator); method public android.view.ViewPropertyAnimator setListener(android.animation.Animator.AnimatorListener); method public android.view.ViewPropertyAnimator setStartDelay(long); + method public android.view.ViewPropertyAnimator setUpdateListener(android.animation.ValueAnimator.AnimatorUpdateListener); method public void start(); method public android.view.ViewPropertyAnimator translationX(float); method public android.view.ViewPropertyAnimator translationXBy(float); @@ -26906,6 +28566,7 @@ package android.view { method public final boolean hasChildren(); method public boolean hasFeature(int); method protected final boolean hasSoftInputMode(); + method public void injectInputEvent(android.view.InputEvent); method public abstract void invalidatePanelMenu(int); method public final boolean isActive(); method public abstract boolean isFloating(); @@ -26941,7 +28602,10 @@ package android.view { method public void setFlags(int, int); method public void setFormat(int); method public void setGravity(int); + method public void setIcon(int); method public void setLayout(int, int); + method public void setLocalFocus(boolean, boolean); + method public void setLogo(int); method public void setSoftInputMode(int); method public abstract void setTitle(java.lang.CharSequence); method public abstract void setTitleColor(int); @@ -27079,6 +28743,7 @@ package android.view { field public static final int FLAG_LAYOUT_IN_OVERSCAN = 33554432; // 0x2000000 field public static final int FLAG_LAYOUT_IN_SCREEN = 256; // 0x100 field public static final int FLAG_LAYOUT_NO_LIMITS = 512; // 0x200 + field public static final int FLAG_LOCAL_FOCUS_MODE = 268435456; // 0x10000000 field public static final int FLAG_NOT_FOCUSABLE = 8; // 0x8 field public static final int FLAG_NOT_TOUCHABLE = 16; // 0x10 field public static final int FLAG_NOT_TOUCH_MODAL = 32; // 0x20 @@ -27088,6 +28753,8 @@ package android.view { field public static final int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000 field public static final int FLAG_SPLIT_TOUCH = 8388608; // 0x800000 field public static final int FLAG_TOUCHABLE_WHEN_WAKING = 64; // 0x40 + field public static final int FLAG_TRANSLUCENT_NAVIGATION = 134217728; // 0x8000000 + field public static final int FLAG_TRANSLUCENT_STATUS = 67108864; // 0x4000000 field public static final int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000 field public static final int FLAG_WATCH_OUTSIDE_TOUCH = 262144; // 0x40000 field public static final int FORMAT_CHANGED = 8; // 0x8 @@ -27135,6 +28802,7 @@ package android.view { field public static final int TYPE_KEYGUARD_DIALOG = 2009; // 0x7d9 field public static final int TYPE_PHONE = 2002; // 0x7d2 field public static final int TYPE_PRIORITY_PHONE = 2007; // 0x7d7 + field public static final int TYPE_PRIVATE_PRESENTATION = 2030; // 0x7ee field public static final int TYPE_SEARCH_BAR = 2001; // 0x7d1 field public static final int TYPE_STATUS_BAR = 2000; // 0x7d0 field public static final int TYPE_STATUS_BAR_PANEL = 2014; // 0x7de @@ -27177,6 +28845,7 @@ package android.view.accessibility { method public int describeContents(); method public static java.lang.String eventTypeToString(int); method public int getAction(); + method public int getContentChangeTypes(); method public long getEventTime(); method public int getEventType(); method public int getMovementGranularity(); @@ -27188,11 +28857,16 @@ package android.view.accessibility { method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent); method public static android.view.accessibility.AccessibilityEvent obtain(); method public void setAction(int); + method public void setContentChangeTypes(int); method public void setEventTime(long); method public void setEventType(int); method public void setMovementGranularity(int); method public void setPackageName(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); + field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4 + field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1 + field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2 + field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0 field public static final android.os.Parcelable.Creator CREATOR; field public static final int INVALID_POSITION = -1; // 0xffffffff field public static final deprecated int MAX_TEXT_LENGTH = 500; // 0x1f4 @@ -27228,6 +28902,7 @@ package android.view.accessibility { public final class AccessibilityManager { method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener); + method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener); method public deprecated java.util.List getAccessibilityServiceList(); method public java.util.List getEnabledAccessibilityServiceList(int); method public java.util.List getInstalledAccessibilityServiceList(); @@ -27235,6 +28910,7 @@ package android.view.accessibility { method public boolean isEnabled(); method public boolean isTouchExplorationEnabled(); method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener); + method public boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener); method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent); } @@ -27242,10 +28918,15 @@ package android.view.accessibility { method public abstract void onAccessibilityStateChanged(boolean); } + public static abstract interface AccessibilityManager.TouchExplorationStateChangeListener { + method public abstract void onTouchExplorationStateChanged(boolean); + } + public class AccessibilityNodeInfo implements android.os.Parcelable { method public void addAction(int); method public void addChild(android.view.View); method public void addChild(android.view.View, int); + method public boolean canOpenPopup(); method public int describeContents(); method public java.util.List findAccessibilityNodeInfosByText(java.lang.String); method public java.util.List findAccessibilityNodeInfosByViewId(java.lang.String); @@ -27257,12 +28938,18 @@ package android.view.accessibility { method public android.view.accessibility.AccessibilityNodeInfo getChild(int); method public int getChildCount(); method public java.lang.CharSequence getClassName(); + method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo(); + method public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo getCollectionItemInfo(); method public java.lang.CharSequence getContentDescription(); + method public android.os.Bundle getExtras(); + method public int getInputType(); method public android.view.accessibility.AccessibilityNodeInfo getLabelFor(); method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy(); + method public int getLiveRegion(); method public int getMovementGranularities(); method public java.lang.CharSequence getPackageName(); method public android.view.accessibility.AccessibilityNodeInfo getParent(); + method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo(); method public java.lang.CharSequence getText(); method public int getTextSelectionEnd(); method public int getTextSelectionStart(); @@ -27272,11 +28959,14 @@ package android.view.accessibility { method public boolean isCheckable(); method public boolean isChecked(); method public boolean isClickable(); + method public boolean isContentInvalid(); + method public boolean isDismissable(); method public boolean isEditable(); method public boolean isEnabled(); method public boolean isFocusable(); method public boolean isFocused(); method public boolean isLongClickable(); + method public boolean isMultiLine(); method public boolean isPassword(); method public boolean isScrollable(); method public boolean isSelected(); @@ -27292,25 +28982,34 @@ package android.view.accessibility { method public void setAccessibilityFocused(boolean); method public void setBoundsInParent(android.graphics.Rect); method public void setBoundsInScreen(android.graphics.Rect); + method public void setCanOpenPopup(boolean); method public void setCheckable(boolean); method public void setChecked(boolean); method public void setClassName(java.lang.CharSequence); method public void setClickable(boolean); + method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo); + method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo); method public void setContentDescription(java.lang.CharSequence); + method public void setContentInvalid(boolean); + method public void setDismissable(boolean); method public void setEditable(boolean); method public void setEnabled(boolean); method public void setFocusable(boolean); method public void setFocused(boolean); + method public void setInputType(int); method public void setLabelFor(android.view.View); method public void setLabelFor(android.view.View, int); method public void setLabeledBy(android.view.View); method public void setLabeledBy(android.view.View, int); + method public void setLiveRegion(int); method public void setLongClickable(boolean); method public void setMovementGranularities(int); + method public void setMultiLine(boolean); method public void setPackageName(java.lang.CharSequence); method public void setParent(android.view.View); method public void setParent(android.view.View, int); method public void setPassword(boolean); + method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo); method public void setScrollable(boolean); method public void setSelected(boolean); method public void setSource(android.view.View); @@ -27330,8 +29029,11 @@ package android.view.accessibility { field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2 field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8 field public static final int ACTION_CLICK = 16; // 0x10 + field public static final int ACTION_COLLAPSE = 524288; // 0x80000 field public static final int ACTION_COPY = 16384; // 0x4000 field public static final int ACTION_CUT = 65536; // 0x10000 + field public static final int ACTION_DISMISS = 1048576; // 0x100000 + field public static final int ACTION_EXPAND = 262144; // 0x40000 field public static final int ACTION_FOCUS = 1; // 0x1 field public static final int ACTION_LONG_CLICK = 32; // 0x20 field public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 256; // 0x100 @@ -27353,10 +29055,38 @@ package android.view.accessibility { field public static final int MOVEMENT_GRANULARITY_WORD = 2; // 0x2 } + public static final class AccessibilityNodeInfo.CollectionInfo { + method public int getColumnCount(); + method public int getRowCount(); + method public boolean isHierarchical(); + method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean); + } + + public static final class AccessibilityNodeInfo.CollectionItemInfo { + method public int getColumnIndex(); + method public int getColumnSpan(); + method public int getRowIndex(); + method public int getRowSpan(); + method public boolean isHeading(); + method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean); + } + + public static final class AccessibilityNodeInfo.RangeInfo { + method public float getCurrent(); + method public float getMax(); + method public float getMin(); + method public int getType(); + method public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float); + field public static final int RANGE_TYPE_FLOAT = 1; // 0x1 + field public static final int RANGE_TYPE_INT = 0; // 0x0 + field public static final int RANGE_TYPE_PERCENT = 2; // 0x2 + } + public abstract class AccessibilityNodeProvider { ctor public AccessibilityNodeProvider(); method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(int); method public java.util.List findAccessibilityNodeInfosByText(java.lang.String, int); + method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method public boolean performAction(int, int, android.os.Bundle); } @@ -27409,6 +29139,34 @@ package android.view.accessibility { method public void setToIndex(int); } + public class CaptioningManager { + method public void addCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener); + method public final float getFontScale(); + method public final java.util.Locale getLocale(); + method public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle(); + method public final boolean isEnabled(); + method public void removeCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener); + } + + public static final class CaptioningManager.CaptionStyle { + method public android.graphics.Typeface getTypeface(); + field public static final int EDGE_TYPE_DROP_SHADOW = 2; // 0x2 + field public static final int EDGE_TYPE_NONE = 0; // 0x0 + field public static final int EDGE_TYPE_OUTLINE = 1; // 0x1 + field public final int backgroundColor; + field public final int edgeColor; + field public final int edgeType; + field public final int foregroundColor; + } + + public static abstract class CaptioningManager.CaptioningChangeListener { + ctor public CaptioningManager.CaptioningChangeListener(); + method public void onEnabledChanged(boolean); + method public void onFontScaleChanged(float); + method public void onLocaleChanged(java.util.Locale); + method public void onUserStyleChanged(android.view.accessibility.CaptioningManager.CaptionStyle); + } + } package android.view.animation { @@ -27661,10 +29419,10 @@ package android.view.animation { method public void setAlpha(float); method public void setTransformationType(int); method public java.lang.String toShortString(); - field public static int TYPE_ALPHA; - field public static int TYPE_BOTH; - field public static int TYPE_IDENTITY; - field public static int TYPE_MATRIX; + field public static final int TYPE_ALPHA = 1; // 0x1 + field public static final int TYPE_BOTH = 3; // 0x3 + field public static final int TYPE_IDENTITY = 0; // 0x0 + field public static final int TYPE_MATRIX = 2; // 0x2 field protected float mAlpha; field protected android.graphics.Matrix mMatrix; field protected int mTransformationType; @@ -27928,6 +29686,7 @@ package android.view.inputmethod { method public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype); method public void setInputMethod(android.os.IBinder, java.lang.String); method public void setInputMethodAndSubtype(android.os.IBinder, java.lang.String, android.view.inputmethod.InputMethodSubtype); + method public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder); method public void showInputMethodAndSubtypeEnabler(java.lang.String); method public void showInputMethodPicker(); method public boolean showSoftInput(android.view.View, int); @@ -27971,8 +29730,8 @@ package android.view.inputmethod { } public final class InputMethodSubtype implements android.os.Parcelable { - ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean); - ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, int); + ctor public deprecated InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean); + ctor public deprecated InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, int); method public boolean containsExtraValueKey(java.lang.String); method public int describeContents(); method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo); @@ -27982,12 +29741,27 @@ package android.view.inputmethod { method public java.lang.String getLocale(); method public java.lang.String getMode(); method public int getNameResId(); + method public boolean isAsciiCapable(); method public boolean isAuxiliary(); method public boolean overridesImplicitlyEnabledSubtype(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } + public static class InputMethodSubtype.InputMethodSubtypeBuilder { + ctor public InputMethodSubtype.InputMethodSubtypeBuilder(); + method public android.view.inputmethod.InputMethodSubtype build(); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeId(int); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeLocale(java.lang.String); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeMode(java.lang.String); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int); + } + } package android.view.textservice { @@ -28226,7 +30000,7 @@ package android.webkit { method public deprecated void onConsoleMessage(java.lang.String, int, java.lang.String); method public boolean onConsoleMessage(android.webkit.ConsoleMessage); method public boolean onCreateWindow(android.webkit.WebView, boolean, boolean, android.os.Message); - method public void onExceededDatabaseQuota(java.lang.String, java.lang.String, long, long, long, android.webkit.WebStorage.QuotaUpdater); + method public deprecated void onExceededDatabaseQuota(java.lang.String, java.lang.String, long, long, long, android.webkit.WebStorage.QuotaUpdater); method public void onGeolocationPermissionsHidePrompt(); method public void onGeolocationPermissionsShowPrompt(java.lang.String, android.webkit.GeolocationPermissions.Callback); method public void onHideCustomView(); @@ -28236,7 +30010,7 @@ package android.webkit { method public boolean onJsPrompt(android.webkit.WebView, java.lang.String, java.lang.String, java.lang.String, android.webkit.JsPromptResult); method public deprecated boolean onJsTimeout(); method public void onProgressChanged(android.webkit.WebView, int); - method public void onReachedMaxAppCacheSize(long, long, android.webkit.WebStorage.QuotaUpdater); + method public deprecated void onReachedMaxAppCacheSize(long, long, android.webkit.WebStorage.QuotaUpdater); method public void onReceivedIcon(android.webkit.WebView, android.graphics.Bitmap); method public void onReceivedTitle(android.webkit.WebView, java.lang.String); method public void onReceivedTouchIconUrl(android.webkit.WebView, java.lang.String, boolean); @@ -28292,12 +30066,12 @@ package android.webkit { method public int getCacheMode(); method public synchronized java.lang.String getCursiveFontFamily(); method public synchronized boolean getDatabaseEnabled(); - method public synchronized java.lang.String getDatabasePath(); + method public deprecated synchronized java.lang.String getDatabasePath(); method public synchronized int getDefaultFixedFontSize(); method public synchronized int getDefaultFontSize(); method public synchronized java.lang.String getDefaultTextEncodingName(); method public static java.lang.String getDefaultUserAgent(android.content.Context); - method public android.webkit.WebSettings.ZoomDensity getDefaultZoom(); + method public deprecated android.webkit.WebSettings.ZoomDensity getDefaultZoom(); method public boolean getDisplayZoomControls(); method public synchronized boolean getDomStorageEnabled(); method public synchronized java.lang.String getFantasyFontFamily(); @@ -28334,11 +30108,11 @@ package android.webkit { method public void setCacheMode(int); method public synchronized void setCursiveFontFamily(java.lang.String); method public synchronized void setDatabaseEnabled(boolean); - method public synchronized void setDatabasePath(java.lang.String); + method public deprecated synchronized void setDatabasePath(java.lang.String); method public synchronized void setDefaultFixedFontSize(int); method public synchronized void setDefaultFontSize(int); method public synchronized void setDefaultTextEncodingName(java.lang.String); - method public void setDefaultZoom(android.webkit.WebSettings.ZoomDensity); + method public deprecated void setDefaultZoom(android.webkit.WebSettings.ZoomDensity); method public void setDisplayZoomControls(boolean); method public synchronized void setDomStorageEnabled(boolean); method public deprecated void setEnableSmoothTransition(boolean); @@ -28381,9 +30155,10 @@ package android.webkit { public static final class WebSettings.LayoutAlgorithm extends java.lang.Enum { method public static android.webkit.WebSettings.LayoutAlgorithm valueOf(java.lang.String); method public static final android.webkit.WebSettings.LayoutAlgorithm[] values(); - enum_constant public static final android.webkit.WebSettings.LayoutAlgorithm NARROW_COLUMNS; + enum_constant public static final deprecated android.webkit.WebSettings.LayoutAlgorithm NARROW_COLUMNS; enum_constant public static final android.webkit.WebSettings.LayoutAlgorithm NORMAL; enum_constant public static final deprecated android.webkit.WebSettings.LayoutAlgorithm SINGLE_COLUMN; + enum_constant public static final android.webkit.WebSettings.LayoutAlgorithm TEXT_AUTOSIZING; } public static final class WebSettings.PluginState extends java.lang.Enum { @@ -28436,7 +30211,7 @@ package android.webkit { method public long getUsage(); } - public static abstract interface WebStorage.QuotaUpdater { + public static abstract deprecated interface WebStorage.QuotaUpdater { method public abstract void updateQuota(long); } @@ -28464,7 +30239,7 @@ package android.webkit { method public boolean canGoForward(); method public deprecated boolean canZoomIn(); method public deprecated boolean canZoomOut(); - method public android.graphics.Picture capturePicture(); + method public deprecated android.graphics.Picture capturePicture(); method public void clearCache(boolean); method public void clearFormData(); method public void clearHistory(); @@ -28472,14 +30247,16 @@ package android.webkit { method public void clearSslPreferences(); method public deprecated void clearView(); method public android.webkit.WebBackForwardList copyBackForwardList(); + method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(); method public void destroy(); method public void documentHasImages(android.os.Message); + method public void evaluateJavascript(java.lang.String, android.webkit.ValueCallback); method public static java.lang.String findAddress(java.lang.String); method public deprecated int findAll(java.lang.String); method public void findAllAsync(java.lang.String); method public void findNext(boolean); method public void flingScroll(int, int); - method public void freeMemory(); + method public deprecated void freeMemory(); method public android.net.http.SslCertificate getCertificate(); method public int getContentHeight(); method public android.graphics.Bitmap getFavicon(); @@ -28532,6 +30309,7 @@ package android.webkit { method public deprecated void setPictureListener(android.webkit.WebView.PictureListener); method public void setVerticalScrollbarOverlay(boolean); method public void setWebChromeClient(android.webkit.WebChromeClient); + method public static void setWebContentsDebuggingEnabled(boolean); method public void setWebViewClient(android.webkit.WebViewClient); method public deprecated boolean showFindDialog(java.lang.String, boolean); method public void stopLoading(); @@ -28630,6 +30408,7 @@ package android.widget { ctor public AbsListView(android.content.Context, android.util.AttributeSet, int); method public void afterTextChanged(android.text.Editable); method public void beforeTextChanged(java.lang.CharSequence, int, int, int); + method public boolean canScrollList(int); method public void clearChoices(); method public void clearTextFilter(); method public void deferNotifyDataSetChanged(); @@ -28661,6 +30440,7 @@ package android.widget { method protected void layoutChildren(); method public void onFilterComplete(int); method public void onGlobalLayout(); + method public void onInitializeAccessibilityNodeInfoForItem(android.view.View, int, android.view.accessibility.AccessibilityNodeInfo); method public boolean onRemoteAdapterConnected(); method public void onRemoteAdapterDisconnected(); method public void onRestoreInstanceState(android.os.Parcelable); @@ -28670,6 +30450,7 @@ package android.widget { method public int pointToPosition(int, int); method public long pointToRowId(int, int); method public void reclaimViews(java.util.List); + method public void scrollListBy(int); method public void setAdapter(android.widget.ListAdapter); method public void setCacheColorHint(int); method public void setChoiceMode(int); @@ -29390,6 +31171,7 @@ package android.widget { ctor public FrameLayout.LayoutParams(int, int, int); ctor public FrameLayout.LayoutParams(android.view.ViewGroup.LayoutParams); ctor public FrameLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams); + ctor public FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams); field public int gravity; } @@ -29652,6 +31434,7 @@ package android.widget { ctor public LinearLayout.LayoutParams(int, int, float); ctor public LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams); ctor public LinearLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams); + ctor public LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams); method public java.lang.String debug(java.lang.String); field public int gravity; field public float weight; @@ -29668,6 +31451,7 @@ package android.widget { ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet, int); ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet, int, int); method public void clearListSelection(); + method public android.view.View.OnTouchListener createDragToOpenListener(android.view.View); method public void dismiss(); method public android.view.View getAnchorView(); method public int getAnimationStyle(); @@ -29697,6 +31481,7 @@ package android.widget { method public void setAnimationStyle(int); method public void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setContentWidth(int); + method public void setDropDownGravity(int); method public void setHeight(int); method public void setHorizontalOffset(int); method public void setInputMethodMode(int); @@ -29729,6 +31514,8 @@ package android.widget { method public void addFooterView(android.view.View); method public void addHeaderView(android.view.View, java.lang.Object, boolean); method public void addHeaderView(android.view.View); + method public boolean areFooterDividersEnabled(); + method public boolean areHeaderDividersEnabled(); method protected android.view.View findViewTraversal(int); method protected android.view.View findViewWithTagTraversal(java.lang.Object); method public android.widget.ListAdapter getAdapter(); @@ -29876,7 +31663,9 @@ package android.widget { public class PopupMenu { ctor public PopupMenu(android.content.Context, android.view.View); + ctor public PopupMenu(android.content.Context, android.view.View, int); method public void dismiss(); + method public android.view.View.OnTouchListener getDragToOpenListener(); method public android.view.Menu getMenu(); method public android.view.MenuInflater getMenuInflater(); method public void inflate(int); @@ -29938,6 +31727,7 @@ package android.widget { method public void setWindowLayoutMode(int, int); method public void showAsDropDown(android.view.View); method public void showAsDropDown(android.view.View, int, int); + method public void showAsDropDown(android.view.View, int, int, int); method public void showAtLocation(android.view.View, int, int, int); method public void update(); method public void update(int, int); @@ -30082,6 +31872,7 @@ package android.widget { ctor public RelativeLayout.LayoutParams(int, int); ctor public RelativeLayout.LayoutParams(android.view.ViewGroup.LayoutParams); ctor public RelativeLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams); + ctor public RelativeLayout.LayoutParams(android.widget.RelativeLayout.LayoutParams); method public void addRule(int); method public void addRule(int, int); method public java.lang.String debug(java.lang.String); @@ -30883,6 +32674,7 @@ package android.widget { ctor public VideoView(android.content.Context); ctor public VideoView(android.content.Context, android.util.AttributeSet); ctor public VideoView(android.content.Context, android.util.AttributeSet, int); + method public void addSubtitleSource(java.io.InputStream, android.media.MediaFormat); method public boolean canPause(); method public boolean canSeekBackward(); method public boolean canSeekForward(); diff --git a/cmds/am/am b/cmds/am/am index c823634982420d4f3dba997657bbb899d3c85de0..1d426bc8c8e1255479f47b874dc4f7581882a7bb 100755 --- a/cmds/am/am +++ b/cmds/am/am @@ -1,3 +1,5 @@ +#!/system/bin/sh +# # Script to start "am" on the device, which has a very rudimentary # shell. # diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 61fe34028a517c29f1abda0ec1f64b39dcc377c5..c18f54259678e8ac4893e20c7083dd78d0ef15d4 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -19,6 +19,7 @@ package com.android.commands.am; import android.app.ActivityManager; +import android.app.ActivityManager.StackBoxInfo; import android.app.ActivityManagerNative; import android.app.IActivityController; import android.app.IActivityManager; @@ -75,6 +76,7 @@ public class Am extends BaseCommand { (new Am()).run(args); } + @Override public void onShowUsage(PrintStream out) { out.println( "usage: am [subcommand] [options]\n" + @@ -82,6 +84,7 @@ public class Am extends BaseCommand { " [--R COUNT] [-S] [--opengl-trace]\n" + " [--user | current] \n" + " am startservice [--user | current] \n" + + " am stopservice [--user | current] \n" + " am force-stop [--user | all | current] \n" + " am kill [--user | all | current] \n" + " am kill-all\n" + @@ -96,11 +99,18 @@ public class Am extends BaseCommand { " am clear-debug-app\n" + " am monitor [--gdb ]\n" + " am hang [--allow-restart]\n" + + " am restart\n" + + " am idle-maintenance\n" + " am screen-compat [on|off] \n" + " am to-uri [INTENT]\n" + " am to-intent-uri [INTENT]\n" + " am switch-user \n" + " am stop-user \n" + + " am stack create \n" + + " am stack movetask [true|false]\n" + + " am stack resize \n" + + " am stack boxes\n" + + " am stack box \n" + "\n" + "am start: start an Activity. Options are:\n" + " -D: enable debugging\n" + @@ -118,6 +128,10 @@ public class Am extends BaseCommand { " --user | current: Specify which user to run as; if not\n" + " specified then run as the current user.\n" + "\n" + + "am stopservice: stop a Service. Options are:\n" + + " --user | current: Specify which user to run as; if not\n" + + " specified then run as the current user.\n" + + "\n" + "am force-stop: force stop everything associated with .\n" + " --user | all | current: Specify user to force stop;\n" + " all users if not specified.\n" + @@ -146,7 +160,7 @@ public class Am extends BaseCommand { " test runners.\n" + " --user | current: Specify user instrumentation runs in;\n" + " current user if not specified.\n" + - " --no-window-animation: turn off window animations will running.\n" + + " --no-window-animation: turn off window animations while running.\n" + "\n" + "am profile: start and stop profiler on a process. The given argument\n" + " may be either a process name or pid. Options are:\n" + @@ -174,6 +188,10 @@ public class Am extends BaseCommand { "am hang: hang the system.\n" + " --allow-restart: allow watchdog to perform normal system restart\n" + "\n" + + "am restart: restart the user-space system.\n" + + "\n" + + "am idle-maintenance: perform idle maintenance now.\n" + + "\n" + "am screen-compat: control screen compatibility mode of .\n" + "\n" + "am to-uri: print the given Intent specification as a URI.\n" + @@ -186,6 +204,25 @@ public class Am extends BaseCommand { "am stop-user: stop execution of USER_ID, not allowing it to run any\n" + " code until a later explicit switch to it.\n" + "\n" + + "am stack create: create a new stack relative to an existing one.\n" + + " : the task to populate the new stack with. Must exist.\n" + + " : existing stack box's id.\n" + + " : 0: before , per RTL/LTR configuration,\n" + + " 1: after , per RTL/LTR configuration,\n" + + " 2: to left of ,\n" + + " 3: to right of ," + + " 4: above , 5: below \n" + + " : float between 0.2 and 0.8 inclusive.\n" + + "\n" + + "am stack movetask: move from its current stack to the top (true) or" + + " bottom (false) of .\n" + + "\n" + + "am stack resize: change relative size to new .\n" + + "\n" + + "am stack boxes: list the hierarchy of stack boxes and their contents.\n" + + "\n" + + "am stack box: list the hierarchy of stack boxes rooted at .\n" + + "\n" + " specifications include these flags and arguments:\n" + " [-a ] [-d ] [-t ]\n" + " [-c [-c ] ...]\n" + @@ -218,6 +255,7 @@ public class Am extends BaseCommand { ); } + @Override public void onRun() throws Exception { mAm = ActivityManagerNative.getDefault(); @@ -232,6 +270,8 @@ public class Am extends BaseCommand { runStart(); } else if (op.equals("startservice")) { runStartService(); + } else if (op.equals("stopservice")) { + runStopService(); } else if (op.equals("force-stop")) { runForceStop(); } else if (op.equals("kill")) { @@ -256,6 +296,10 @@ public class Am extends BaseCommand { runMonitor(); } else if (op.equals("hang")) { runHang(); + } else if (op.equals("restart")) { + runRestart(); + } else if (op.equals("idle-maintenance")) { + runIdleMaintenance(); } else if (op.equals("screen-compat")) { runScreenCompat(); } else if (op.equals("to-uri")) { @@ -266,6 +310,8 @@ public class Am extends BaseCommand { runSwitchUser(); } else if (op.equals("stop-user")) { runStopUser(); + } else if (op.equals("stack")) { + runStack(); } else { showError("Error: unknown command '" + op + "'"); } @@ -545,6 +591,23 @@ public class Am extends BaseCommand { } } + private void runStopService() throws Exception { + Intent intent = makeIntent(UserHandle.USER_CURRENT); + if (mUserId == UserHandle.USER_ALL) { + System.err.println("Error: Can't stop activity with user 'all'"); + return; + } + System.out.println("Stopping service: " + intent); + int result = mAm.stopService(null, intent, intent.getType(), mUserId); + if (result == 0) { + System.err.println("Service not stopped: was not running."); + } else if (result == 1) { + System.err.println("Service stopped"); + } else if (result == -1) { + System.err.println("Error stopping service"); + } + } + private void runStart() throws Exception { Intent intent = makeIntent(UserHandle.USER_CURRENT); @@ -1036,7 +1099,7 @@ public class Am extends BaseCommand { } @Override - public boolean activityResuming(String pkg) throws RemoteException { + public boolean activityResuming(String pkg) { synchronized (this) { System.out.println("** Activity resuming: " + pkg); } @@ -1044,7 +1107,7 @@ public class Am extends BaseCommand { } @Override - public boolean activityStarting(Intent intent, String pkg) throws RemoteException { + public boolean activityStarting(Intent intent, String pkg) { synchronized (this) { System.out.println("** Activity starting: " + pkg); } @@ -1053,7 +1116,7 @@ public class Am extends BaseCommand { @Override public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, - long timeMillis, String stackTrace) throws RemoteException { + long timeMillis, String stackTrace) { synchronized (this) { System.out.println("** ERROR: PROCESS CRASHED"); System.out.println("processName: " + processName); @@ -1070,8 +1133,7 @@ public class Am extends BaseCommand { } @Override - public int appEarlyNotResponding(String processName, int pid, String annotation) - throws RemoteException { + public int appEarlyNotResponding(String processName, int pid, String annotation) { synchronized (this) { System.out.println("** ERROR: EARLY PROCESS NOT RESPONDING"); System.out.println("processName: " + processName); @@ -1084,8 +1146,7 @@ public class Am extends BaseCommand { } @Override - public int appNotResponding(String processName, int pid, String processStats) - throws RemoteException { + public int appNotResponding(String processName, int pid, String processStats) { synchronized (this) { System.out.println("** ERROR: PROCESS NOT RESPONDING"); System.out.println("processName: " + processName); @@ -1101,8 +1162,7 @@ public class Am extends BaseCommand { } @Override - public int systemNotResponding(String message) - throws RemoteException { + public int systemNotResponding(String message) { synchronized (this) { System.out.println("** ERROR: PROCESS NOT RESPONDING"); System.out.println("message: " + message); @@ -1327,6 +1387,31 @@ public class Am extends BaseCommand { mAm.hang(new Binder(), allowRestart); } + private void runRestart() throws Exception { + String opt; + while ((opt=nextOption()) != null) { + System.err.println("Error: Unknown option: " + opt); + return; + } + + System.out.println("Restart the system..."); + mAm.restart(); + } + + private void runIdleMaintenance() throws Exception { + String opt; + while ((opt=nextOption()) != null) { + System.err.println("Error: Unknown option: " + opt); + return; + } + + System.out.println("Performing idle maintenance..."); + Intent intent = new Intent( + "com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE"); + mAm.broadcastIntent(null, intent, null, null, 0, null, null, null, + android.app.AppOpsManager.OP_NONE, true, false, UserHandle.USER_ALL); + } + private void runScreenCompat() throws Exception { String mode = nextArgRequired(); boolean enabled; @@ -1361,7 +1446,7 @@ public class Am extends BaseCommand { @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, - boolean ordered, boolean sticky, int sendingUser) throws RemoteException { + boolean ordered, boolean sticky, int sendingUser) { String line = "Broadcast completed: result=" + resultCode; if (data != null) line = line + ", data=\"" + data + "\""; if (extras != null) line = line + ", extras: " + extras; @@ -1394,6 +1479,7 @@ public class Am extends BaseCommand { mRawMode = rawMode; } + @Override public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) { synchronized (this) { // pretty printer mode? @@ -1416,6 +1502,7 @@ public class Am extends BaseCommand { } } + @Override public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) { synchronized (this) { @@ -1456,4 +1543,93 @@ public class Am extends BaseCommand { return true; } } + + private void runStack() throws Exception { + String op = nextArgRequired(); + if (op.equals("create")) { + runStackCreate(); + } else if (op.equals("movetask")) { + runStackMoveTask(); + } else if (op.equals("resize")) { + runStackBoxResize(); + } else if (op.equals("boxes")) { + runStackBoxes(); + } else if (op.equals("box")) { + runStackBoxInfo(); + } else { + showError("Error: unknown command '" + op + "'"); + return; + } + } + + private void runStackCreate() throws Exception { + String taskIdStr = nextArgRequired(); + int taskId = Integer.valueOf(taskIdStr); + String relativeToStr = nextArgRequired(); + int relativeTo = Integer.valueOf(relativeToStr); + String positionStr = nextArgRequired(); + int position = Integer.valueOf(positionStr); + String weightStr = nextArgRequired(); + float weight = Float.valueOf(weightStr); + + try { + int stackId = mAm.createStack(taskId, relativeTo, position, weight); + System.out.println("createStack returned new stackId=" + stackId + "\n\n"); + } catch (RemoteException e) { + } + } + + private void runStackMoveTask() throws Exception { + String taskIdStr = nextArgRequired(); + int taskId = Integer.valueOf(taskIdStr); + String stackIdStr = nextArgRequired(); + int stackId = Integer.valueOf(stackIdStr); + String toTopStr = nextArgRequired(); + final boolean toTop; + if ("true".equals(toTopStr)) { + toTop = true; + } else if ("false".equals(toTopStr)) { + toTop = false; + } else { + System.err.println("Error: bad toTop arg: " + toTopStr); + return; + } + + try { + mAm.moveTaskToStack(taskId, stackId, toTop); + } catch (RemoteException e) { + } + } + + private void runStackBoxResize() throws Exception { + String stackBoxIdStr = nextArgRequired(); + int stackBoxId = Integer.valueOf(stackBoxIdStr); + String weightStr = nextArgRequired(); + float weight = Float.valueOf(weightStr); + + try { + mAm.resizeStackBox(stackBoxId, weight); + } catch (RemoteException e) { + } + } + + private void runStackBoxes() throws Exception { + try { + List stackBoxes = mAm.getStackBoxes(); + for (StackBoxInfo info : stackBoxes) { + System.out.println(info); + } + } catch (RemoteException e) { + } + } + + private void runStackBoxInfo() throws Exception { + try { + String stackBoxIdStr = nextArgRequired(); + int stackBoxId = Integer.valueOf(stackBoxIdStr); + StackBoxInfo stackBoxInfo = mAm.getStackBoxInfo(stackBoxId); + System.out.println(stackBoxInfo); + } catch (RemoteException e) { + } + } } diff --git a/cmds/backup/Android.mk b/cmds/backup/Android.mk index 73af0bc0a6caf21b43491257f64e629dca1e5467..42e513345d9cc097e2173ede262c34dd7010664d 100644 --- a/cmds/backup/Android.mk +++ b/cmds/backup/Android.mk @@ -10,6 +10,6 @@ LOCAL_SHARED_LIBRARIES := libcutils libutils libandroidfw LOCAL_MODULE:= btool LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional include $(BUILD_EXECUTABLE) diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index cc49cf3b61e684000e1566a2e1551c7361201b3f..0f610e975b647414761ab735bb6a1aa90ed8ee8b 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -44,7 +44,7 @@ #include #include -#include +#include #include #include diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java index 80ac53962c885f050ccc53d623e265d1d9df57c7..2a7c79bdfb5b534403faca0c2e50d884b8f38fd5 100644 --- a/cmds/input/src/com/android/commands/input/Input.java +++ b/cmds/input/src/com/android/commands/input/Input.java @@ -24,6 +24,9 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import java.util.HashMap; +import java.util.Map; + /** * Command that sends key events to the device, either by their keycode, or by * desired character output. @@ -31,6 +34,21 @@ import android.view.MotionEvent; public class Input { private static final String TAG = "Input"; + private static final String INVALID_ARGUMENTS = "Error: Invalid arguments for command: "; + + private static final Map SOURCES = new HashMap() {{ + put("keyboard", InputDevice.SOURCE_KEYBOARD); + put("dpad", InputDevice.SOURCE_DPAD); + put("gamepad", InputDevice.SOURCE_GAMEPAD); + put("touchscreen", InputDevice.SOURCE_TOUCHSCREEN); + put("mouse", InputDevice.SOURCE_MOUSE); + put("stylus", InputDevice.SOURCE_STYLUS); + put("trackball", InputDevice.SOURCE_TRACKBALL); + put("touchpad", InputDevice.SOURCE_TOUCHPAD); + put("touchnavigation", InputDevice.SOURCE_TOUCH_NAVIGATION); + put("joystick", InputDevice.SOURCE_JOYSTICK); + }}; + /** * Command-line entry point. @@ -47,82 +65,71 @@ public class Input { return; } - String command = args[0]; + int index = 0; + String command = args[index]; + int inputSource = InputDevice.SOURCE_UNKNOWN; + if (SOURCES.containsKey(command)) { + inputSource = SOURCES.get(command); + index++; + command = args[index]; + } + final int length = args.length - index; try { if (command.equals("text")) { - if (args.length == 2) { - sendText(args[1]); + if (length == 2) { + inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD); + sendText(inputSource, args[index+1]); return; } } else if (command.equals("keyevent")) { - if (args.length >= 2) { - for (int i=1; i < args.length; i++) { - int keyCode = KeyEvent.keyCodeFromString(args[i]); - if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { - keyCode = KeyEvent.keyCodeFromString("KEYCODE_" + args[i]); + if (length >= 2) { + final boolean longpress = "--longpress".equals(args[index + 1]); + final int start = longpress ? index + 2 : index + 1; + inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD); + if (length > start) { + for (int i = start; i < length; i++) { + int keyCode = KeyEvent.keyCodeFromString(args[i]); + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + keyCode = KeyEvent.keyCodeFromString("KEYCODE_" + args[i]); + } + sendKeyEvent(inputSource, keyCode, longpress); } - sendKeyEvent(keyCode); + return; } - return; } } else if (command.equals("tap")) { - if (args.length == 3) { - sendTap(InputDevice.SOURCE_TOUCHSCREEN, Float.parseFloat(args[1]), Float.parseFloat(args[2])); + if (length == 3) { + inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); + sendTap(inputSource, Float.parseFloat(args[index+1]), + Float.parseFloat(args[index+2])); return; } } else if (command.equals("swipe")) { - if (args.length == 5) { - sendSwipe(InputDevice.SOURCE_TOUCHSCREEN, Float.parseFloat(args[1]), Float.parseFloat(args[2]), - Float.parseFloat(args[3]), Float.parseFloat(args[4]), -1); - return; - } - } else if (command.equals("touchscreen") || command.equals("touchpad") - || command.equals("touchnavigation")) { - // determine input source - int inputSource = InputDevice.SOURCE_TOUCHSCREEN; - if (command.equals("touchpad")) { - inputSource = InputDevice.SOURCE_TOUCHPAD; - } else if (command.equals("touchnavigation")) { - inputSource = InputDevice.SOURCE_TOUCH_NAVIGATION; + int duration = -1; + inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); + switch (length) { + case 6: + duration = Integer.parseInt(args[index+5]); + case 5: + sendSwipe(inputSource, + Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]), + Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]), + duration); + return; } - // determine subcommand - if (args.length > 1) { - String subcommand = args[1]; - if (subcommand.equals("tap")) { - if (args.length == 4) { - sendTap(inputSource, Float.parseFloat(args[2]), - Float.parseFloat(args[3])); - return; - } - } else if (subcommand.equals("swipe")) { - if (args.length == 6) { - sendSwipe(inputSource, Float.parseFloat(args[2]), - Float.parseFloat(args[3]), Float.parseFloat(args[4]), - Float.parseFloat(args[5]), -1); - return; - } else if (args.length == 7) { - sendSwipe(inputSource, Float.parseFloat(args[2]), - Float.parseFloat(args[3]), Float.parseFloat(args[4]), - Float.parseFloat(args[5]), Integer.parseInt(args[6])); - return; - } - } + } else if (command.equals("press")) { + inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL); + if (length == 1) { + sendTap(inputSource, 0.0f, 0.0f); + return; } - } else if (command.equals("trackball")) { - // determine subcommand - if (args.length > 1) { - String subcommand = args[1]; - if (subcommand.equals("press")) { - sendTap(InputDevice.SOURCE_TRACKBALL, 0.0f, 0.0f); - return; - } else if (subcommand.equals("roll")) { - if (args.length == 4) { - sendMove(InputDevice.SOURCE_TRACKBALL, Float.parseFloat(args[2]), - Float.parseFloat(args[3])); - return; - } - } + } else if (command.equals("roll")) { + inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL); + if (length == 3) { + sendMove(inputSource, Float.parseFloat(args[index+1]), + Float.parseFloat(args[index+2])); + return; } } else { System.err.println("Error: Unknown command: " + command); @@ -131,7 +138,7 @@ public class Input { } } catch (NumberFormatException ex) { } - System.err.println("Error: Invalid arguments for command: " + command); + System.err.println(INVALID_ARGUMENTS + command); showUsage(); } @@ -141,7 +148,7 @@ public class Input { * * @param text is a string of characters you want to input to the device. */ - private void sendText(String text) { + private void sendText(int source, String text) { StringBuffer buff = new StringBuffer(text); @@ -153,7 +160,7 @@ public class Input { buff.setCharAt(i, ' '); buff.deleteCharAt(--i); } - } + } if (buff.charAt(i) == '%') { escapeFlag = true; } @@ -164,16 +171,25 @@ public class Input { KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); KeyEvent[] events = kcm.getEvents(chars); for(int i = 0; i < events.length; i++) { - injectKeyEvent(events[i]); + KeyEvent e = events[i]; + if (source != e.getSource()) { + e.setSource(source); + } + injectKeyEvent(e); } } - private void sendKeyEvent(int keyCode) { + private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) { long now = SystemClock.uptimeMillis(); injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource)); + if (longpress) { + injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS, + inputSource)); + } injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource)); } private void sendTap(int inputSource, float x, float y) { @@ -197,7 +213,7 @@ public class Input { lerp(y1, y2, alpha), 1.0f); now = SystemClock.uptimeMillis(); } - injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x1, y1, 0.0f); + injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f); } /** @@ -248,14 +264,26 @@ public class Input { return (b - a) * alpha + a; } + private static final int getSource(int inputSource, int defaultSource) { + return inputSource == InputDevice.SOURCE_UNKNOWN ? defaultSource : inputSource; + } + private void showUsage() { - System.err.println("usage: input ..."); - System.err.println(" input text "); - System.err.println(" input keyevent ..."); - System.err.println(" input [touchscreen|touchpad|touchnavigation] tap "); - System.err.println(" input [touchscreen|touchpad|touchnavigation] swipe " - + " [duration(ms)]"); - System.err.println(" input trackball press"); - System.err.println(" input trackball roll "); + System.err.println("Usage: input [] [...]"); + System.err.println(); + System.err.println("The sources are: "); + for (String src : SOURCES.keySet()) { + System.err.println(" " + src); + } + System.err.println(); + System.err.println("The commands and default sources are:"); + System.err.println(" text (Default: touchscreen)"); + System.err.println(" keyevent [--longpress] ..." + + " (Default: keyboard)"); + System.err.println(" tap (Default: touchscreen)"); + System.err.println(" swipe [duration(ms)]" + + " (Default: touchscreen)"); + System.err.println(" press (Default: trackball)"); + System.err.println(" roll (Default: trackball)"); } } diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java index 56af7d65eb3521a281b752d31c8295ba2b8f2778..92c6a51e88498fd27240ec8ea384b988d15b1367 100644 --- a/cmds/media/src/com/android/commands/media/Media.java +++ b/cmds/media/src/com/android/commands/media/Media.java @@ -139,6 +139,11 @@ public class Media extends BaseCommand { + " intent=" + clientMediaIntent + " clearing=" + clearing); } + @Override + public void setEnabled(boolean enabled) { + System.out.println("New enable state= " + (enabled ? "enabled" : "disabled")); + } + @Override public void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed) { diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 224945ad4901a826de7db90420b3c568b7a5fd04..d1ded10d7633d6afde7ddb765cba80cc05422dcb 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -16,8 +16,7 @@ package com.android.commands.pm; -import com.android.internal.content.PackageHelper; - +import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.content.ComponentName; import android.content.pm.ApplicationInfo; @@ -46,6 +45,7 @@ import android.os.UserHandle; import android.os.UserManager; import java.io.File; +import java.io.FileDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.security.InvalidAlgorithmParameterException; @@ -59,6 +59,8 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import com.android.internal.content.PackageHelper; + public final class Pm { IPackageManager mPm; IUserManager mUm; @@ -105,6 +107,11 @@ public final class Pm { return; } + if ("dump".equals(op)) { + runDump(); + return; + } + if ("install".equals(op)) { runInstall(); return; @@ -140,6 +147,16 @@ public final class Pm { return; } + if ("block".equals(op)) { + runSetBlockedSetting(true); + return; + } + + if ("unblock".equals(op)) { + runSetBlockedSetting(false); + return; + } + if ("grant".equals(op)) { runGrantRevokePermission(true); return; @@ -672,6 +689,15 @@ public final class Pm { displayPackageFilePath(pkg); } + private void runDump() { + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package specified"); + return; + } + ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg); + } + class PackageInstallObserver extends IPackageInstallObserver.Stub { boolean finished; int result; @@ -1240,6 +1266,36 @@ public final class Pm { } } + private void runSetBlockedSetting(boolean state) { + int userId = 0; + String option = nextOption(); + if (option != null && option.equals("--user")) { + String optionData = nextOptionData(); + if (optionData == null || !isNumber(optionData)) { + System.err.println("Error: no USER_ID specified"); + showUsage(); + return; + } else { + userId = Integer.parseInt(optionData); + } + } + + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package or component specified"); + showUsage(); + return; + } + try { + mPm.setApplicationBlockedSettingAsUser(pkg, state, userId); + System.err.println("Package " + pkg + " new blocked state: " + + mPm.getApplicationBlockedSettingAsUser(pkg, userId)); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + private void runGrantRevokePermission(boolean grant) { String pkg = nextArg(); if (pkg == null) { @@ -1456,6 +1512,7 @@ public final class Pm { System.err.println(" pm list libraries"); System.err.println(" pm list users"); System.err.println(" pm path PACKAGE"); + System.err.println(" pm dump PACKAGE"); System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f]"); System.err.println(" [--algo --key --iv ]"); System.err.println(" [--originating-uri ] [--referrer ] PATH"); @@ -1465,6 +1522,8 @@ public final class Pm { System.err.println(" pm disable [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT"); + System.err.println(" pm block [--user USER_ID] PACKAGE_OR_COMPONENT"); + System.err.println(" pm unblock [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm grant PACKAGE PERMISSION"); System.err.println(" pm revoke PACKAGE PERMISSION"); System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]"); @@ -1506,6 +1565,8 @@ public final class Pm { System.err.println(""); System.err.println("pm path: print the path to the .apk of the given PACKAGE."); System.err.println(""); + System.err.println("pm dump: print system state associated w ith the given PACKAGE."); + System.err.println(""); System.err.println("pm install: installs a package to the system. Options:"); System.err.println(" -l: install the package with FORWARD_LOCK."); System.err.println(" -r: reinstall an exisiting app, keeping its data."); diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index be32355cb9658161d85e1a6aa27e15e2132cfa64..a57de0179d515dbb7126404fe60ce56dac7af34a 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -55,12 +55,8 @@ static void usage(const char* pname) static SkBitmap::Config flinger2skia(PixelFormat f) { switch (f) { - case PIXEL_FORMAT_A_8: - return SkBitmap::kA8_Config; case PIXEL_FORMAT_RGB_565: return SkBitmap::kRGB_565_Config; - case PIXEL_FORMAT_RGBA_4444: - return SkBitmap::kARGB_4444_Config; default: return SkBitmap::kARGB_8888_Config; } diff --git a/cmds/system_server/Android.mk b/cmds/system_server/Android.mk deleted file mode 100644 index 3083e31acb4124e14e0b1a9374d7925460f1911e..0000000000000000000000000000000000000000 --- a/cmds/system_server/Android.mk +++ /dev/null @@ -1,20 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - system_main.cpp - -LOCAL_SHARED_LIBRARIES := \ - libutils \ - libbinder \ - libsystem_server \ - liblog - -LOCAL_C_INCLUDES := \ - $(JNI_H_INCLUDE) - -LOCAL_MODULE:= system_server - -include $(BUILD_EXECUTABLE) - -include $(LOCAL_PATH)/library/Android.mk diff --git a/cmds/system_server/library/Android.mk b/cmds/system_server/library/Android.mk deleted file mode 100644 index d78474e90815f83fc24cbb619954ced7a5b8bb08..0000000000000000000000000000000000000000 --- a/cmds/system_server/library/Android.mk +++ /dev/null @@ -1,27 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - system_init.cpp - -base = $(LOCAL_PATH)/../../.. -native = $(LOCAL_PATH)/../../../../native - -LOCAL_C_INCLUDES := \ - $(native)/services/sensorservice \ - $(native)/services/surfaceflinger \ - $(JNI_H_INCLUDE) - -LOCAL_SHARED_LIBRARIES := \ - libandroid_runtime \ - libsensorservice \ - libsurfaceflinger \ - libinput \ - libutils \ - libbinder \ - libcutils \ - liblog - -LOCAL_MODULE:= libsystem_server - -include $(BUILD_SHARED_LIBRARY) diff --git a/cmds/system_server/library/system_init.cpp b/cmds/system_server/library/system_init.cpp deleted file mode 100644 index 745c34a0478c0056a69f79e729b9433d60995a50..0000000000000000000000000000000000000000 --- a/cmds/system_server/library/system_init.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * System server main initialization. - * - * The system server is responsible for becoming the Binder - * context manager, supplying the root ServiceManager object - * through which other services can be found. - */ - -#define LOG_TAG "sysproc" - -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -using namespace android; - -namespace android { -/** - * This class is used to kill this process when the runtime dies. - */ -class GrimReaper : public IBinder::DeathRecipient { -public: - GrimReaper() { } - - virtual void binderDied(const wp& who) - { - ALOGI("Grim Reaper killing system_server..."); - kill(getpid(), SIGKILL); - } -}; - -} // namespace android - - - -extern "C" status_t system_init() -{ - ALOGI("Entered system_init()"); - - sp proc(ProcessState::self()); - - sp sm = defaultServiceManager(); - ALOGI("ServiceManager: %p\n", sm.get()); - - sp grim = new GrimReaper(); - sm->asBinder()->linkToDeath(grim, grim.get(), 0); - - char propBuf[PROPERTY_VALUE_MAX]; - property_get("system_init.startsurfaceflinger", propBuf, "1"); - if (strcmp(propBuf, "1") == 0) { - // Start the SurfaceFlinger - SurfaceFlinger::instantiate(); - } - - property_get("system_init.startsensorservice", propBuf, "1"); - if (strcmp(propBuf, "1") == 0) { - // Start the sensor service - SensorService::instantiate(); - } - - // And now start the Android runtime. We have to do this bit - // of nastiness because the Android runtime initialization requires - // some of the core system services to already be started. - // All other servers should just start the Android runtime at - // the beginning of their processes's main(), before calling - // the init function. - ALOGI("System server: starting Android runtime.\n"); - AndroidRuntime* runtime = AndroidRuntime::getRuntime(); - - ALOGI("System server: starting Android services.\n"); - JNIEnv* env = runtime->getJNIEnv(); - if (env == NULL) { - return UNKNOWN_ERROR; - } - jclass clazz = env->FindClass("com/android/server/SystemServer"); - if (clazz == NULL) { - return UNKNOWN_ERROR; - } - jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V"); - if (methodId == NULL) { - return UNKNOWN_ERROR; - } - env->CallStaticVoidMethod(clazz, methodId); - - ALOGI("System server: entering thread pool.\n"); - ProcessState::self()->startThreadPool(); - IPCThreadState::self()->joinThreadPool(); - ALOGI("System server: exiting thread pool.\n"); - - return NO_ERROR; -} diff --git a/cmds/system_server/system_main.cpp b/cmds/system_server/system_main.cpp deleted file mode 100644 index ddff065c916d394c63f6da3a8c6df961bce256b3..0000000000000000000000000000000000000000 --- a/cmds/system_server/system_main.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Main entry of system server process. - * - * Calls the standard system initialization function, and then - * puts the main thread into the thread pool so it can handle - * incoming transactions. - * - */ - -#define LOG_TAG "sysproc" - -#include -#include - -#include - -#include -#include - -#include -#include -#include - -using namespace android; - -extern "C" status_t system_init(); - -bool finish_system_init() -{ - return true; -} - -static void blockSignals() -{ - sigset_t mask; - int cc; - - sigemptyset(&mask); - sigaddset(&mask, SIGQUIT); - sigaddset(&mask, SIGUSR1); - cc = sigprocmask(SIG_BLOCK, &mask, NULL); - assert(cc == 0); -} - -int main(int argc, const char* const argv[]) -{ - ALOGI("System server is starting with pid=%d.\n", getpid()); - - blockSignals(); - - // You can trust me, honestly! - ALOGW("*** Current priority: %d\n", getpriority(PRIO_PROCESS, 0)); - setpriority(PRIO_PROCESS, 0, -1); - - system_init(); -} diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 059945fc66f42ed01efead452ab01775f6900cc2..bdc4fdde80f4a5bce42636373251f43311e49fa3 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -178,12 +178,13 @@ public class AccessibilityServiceInfo implements Parcelable { * If this flag is set the system will regard views that are not important * for accessibility in addition to the ones that are important for accessibility. * That is, views that are marked as not important for accessibility via - * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} and views that are marked as - * potentially important for accessibility via + * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} or + * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} and views that are + * marked as potentially important for accessibility via * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined - * that are not important for accessibility, are both reported while querying the - * window content and also the accessibility service will receive accessibility events - * from them. + * that are not important for accessibility, are reported while querying the window + * content and also the accessibility service will receive accessibility events from + * them. *

* Note: For accessibility services targeting API version * {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 58eb66f8d390068ca0329b7188785e8766c549f1..82c2159d25f474c3904f19d80c939300d12475af 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -29,6 +29,7 @@ import android.os.UserManager; import android.text.TextUtils; import android.util.Log; import android.view.View; +import android.view.Window; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; @@ -127,6 +128,7 @@ public class ChooseTypeAndAccountActivity extends Activity private int mCallingUid; private String mCallingPackage; private boolean mDisallowAddAccounts; + private boolean mDontShowPicker; @Override public void onCreate(Bundle savedInstanceState) { @@ -189,11 +191,23 @@ public class ChooseTypeAndAccountActivity extends Activity mSetOfRelevantAccountTypes = getReleventAccountTypes(intent); mAlwaysPromptForAccount = intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false); mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); + + // Need to do this once here to request the window feature. Can't do it in onResume + mAccounts = getAcceptableAccountChoices(AccountManager.get(this)); + if (mAccounts.isEmpty() + && mDisallowAddAccounts) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.app_not_authorized); + mDontShowPicker = true; + } } @Override protected void onResume() { super.onResume(); + + if (mDontShowPicker) return; + final AccountManager accountManager = AccountManager.get(this); mAccounts = getAcceptableAccountChoices(accountManager); @@ -206,11 +220,6 @@ public class ChooseTypeAndAccountActivity extends Activity // If there are no relevant accounts and only one relevant account type go directly to // add account. Otherwise let the user choose. if (mAccounts.isEmpty()) { - if (mDisallowAddAccounts) { - setContentView(R.layout.app_not_authorized); - setTitle(R.string.error_message_title); - return; - } if (mSetOfRelevantAccountTypes.size() == 1) { runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next()); } else { diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 39eb8d6abe843e9e98ad44842833700d41af355b..129e52c5c6af2fce82d6d81ec9ae0244bf7ef4a0 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -29,6 +29,17 @@ public abstract class Animator implements Cloneable { */ ArrayList mListeners = null; + /** + * The set of listeners to be sent pause/resume events through the life + * of an animation. + */ + ArrayList mPauseListeners = null; + + /** + * Whether this animator is currently in a paused state. + */ + boolean mPaused = false; + /** * Starts this animation. If the animation has a nonzero startDelay, the animation will start * running after that delay elapses. A non-delayed animation will have its initial @@ -68,6 +79,66 @@ public abstract class Animator implements Cloneable { public void end() { } + /** + * Pauses a running animation. This method should only be called on the same thread on + * which the animation was started. If the animation has not yet been {@link + * #isStarted() started} or has since ended, then the call is ignored. Paused + * animations can be resumed by calling {@link #resume()}. + * + * @see #resume() + * @see #isPaused() + * @see AnimatorPauseListener + */ + public void pause() { + if (isStarted() && !mPaused) { + mPaused = true; + if (mPauseListeners != null) { + ArrayList tmpListeners = + (ArrayList) mPauseListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationPause(this); + } + } + } + } + + /** + * Resumes a paused animation, causing the animator to pick up where it left off + * when it was paused. This method should only be called on the same thread on + * which the animation was started. Calls to resume() on an animator that is + * not currently paused will be ignored. + * + * @see #pause() + * @see #isPaused() + * @see AnimatorPauseListener + */ + public void resume() { + if (mPaused) { + mPaused = false; + if (mPauseListeners != null) { + ArrayList tmpListeners = + (ArrayList) mPauseListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationResume(this); + } + } + } + } + + /** + * Returns whether this animator is currently in a paused state. + * + * @return True if the animator is currently paused, false otherwise. + * + * @see #pause() + * @see #resume() + */ + public boolean isPaused() { + return mPaused; + } + /** * The amount of time, in milliseconds, to delay processing the animation * after {@link #start()} is called. @@ -180,15 +251,48 @@ public abstract class Animator implements Cloneable { } /** - * Removes all listeners from this object. This is equivalent to calling - * getListeners() followed by calling clear() on the - * returned list of listeners. + * Adds a pause listener to this animator. + * + * @param listener the listener to be added to the current set of pause listeners + * for this animation. + */ + public void addPauseListener(AnimatorPauseListener listener) { + if (mPauseListeners == null) { + mPauseListeners = new ArrayList(); + } + mPauseListeners.add(listener); + } + + /** + * Removes a pause listener from the set listening to this animation. + * + * @param listener the listener to be removed from the current set of pause + * listeners for this animation. + */ + public void removePauseListener(AnimatorPauseListener listener) { + if (mPauseListeners == null) { + return; + } + mPauseListeners.remove(listener); + if (mPauseListeners.size() == 0) { + mPauseListeners = null; + } + } + + /** + * Removes all {@link #addListener(android.animation.Animator.AnimatorListener) listeners} + * and {@link #addPauseListener(android.animation.Animator.AnimatorPauseListener) + * pauseListeners} from this object. */ public void removeAllListeners() { if (mListeners != null) { mListeners.clear(); mListeners = null; } + if (mPauseListeners != null) { + mPauseListeners.clear(); + mPauseListeners = null; + } } @Override @@ -203,6 +307,14 @@ public abstract class Animator implements Cloneable { anim.mListeners.add(oldListeners.get(i)); } } + if (mPauseListeners != null) { + ArrayList oldListeners = mPauseListeners; + anim.mPauseListeners = new ArrayList(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mPauseListeners.add(oldListeners.get(i)); + } + } return anim; } catch (CloneNotSupportedException e) { throw new AssertionError(); @@ -280,4 +392,29 @@ public abstract class Animator implements Cloneable { */ void onAnimationRepeat(Animator animation); } + + /** + * A pause listener receives notifications from an animation when the + * animation is {@link #pause() paused} or {@link #resume() resumed}. + * + * @see #addPauseListener(AnimatorPauseListener) + */ + public static interface AnimatorPauseListener { + /** + *

Notifies that the animation was paused.

+ * + * @param animation The animaton being paused. + * @see #pause() + */ + void onAnimationPause(Animator animation); + + /** + *

Notifies that the animation was resumed, after being + * previously paused.

+ * + * @param animation The animation being resumed. + * @see #resume() + */ + void onAnimationResume(Animator animation); + } } diff --git a/core/java/android/animation/AnimatorListenerAdapter.java b/core/java/android/animation/AnimatorListenerAdapter.java index e5d70a4f017d4bd0bec7a1c8b558bd92fcf53f35..2ecb8c3dd40905b2f57ca9a614313be473614e50 100644 --- a/core/java/android/animation/AnimatorListenerAdapter.java +++ b/core/java/android/animation/AnimatorListenerAdapter.java @@ -21,7 +21,8 @@ package android.animation; * Any custom listener that cares only about a subset of the methods of this listener can * simply subclass this adapter class instead of implementing the interface directly. */ -public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener { +public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener, + Animator.AnimatorPauseListener { /** * {@inheritDoc} @@ -51,4 +52,17 @@ public abstract class AnimatorListenerAdapter implements Animator.AnimatorListen public void onAnimationStart(Animator animation) { } + /** + * {@inheritDoc} + */ + @Override + public void onAnimationPause(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationResume(Animator animation) { + } } diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index b48853b4f0f01c4012432868858f77f29e6836a5..018a2d66ee131c8f2f2d75efe51131d4739f1352 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -455,6 +455,36 @@ public final class AnimatorSet extends Animator { } } + @Override + public void pause() { + boolean previouslyPaused = mPaused; + super.pause(); + if (!previouslyPaused && mPaused) { + if (mDelayAnim != null) { + mDelayAnim.pause(); + } else { + for (Node node : mNodes) { + node.animation.pause(); + } + } + } + } + + @Override + public void resume() { + boolean previouslyPaused = mPaused; + super.resume(); + if (previouslyPaused && !mPaused) { + if (mDelayAnim != null) { + mDelayAnim.resume(); + } else { + for (Node node : mNodes) { + node.animation.resume(); + } + } + } + } + /** * {@inheritDoc} * @@ -467,6 +497,7 @@ public final class AnimatorSet extends Animator { public void start() { mTerminated = false; mStarted = true; + mPaused = false; if (mDuration >= 0) { // If the duration was set on this AnimatorSet, pass it along to all child animations @@ -549,6 +580,7 @@ public final class AnimatorSet extends Animator { mPlayingSet.add(node.animation); } } + mDelayAnim = null; } }); mDelayAnim.start(); @@ -787,6 +819,7 @@ public final class AnimatorSet extends Animator { } } mAnimatorSet.mStarted = false; + mAnimatorSet.mPaused = false; } } } diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index d8f9e493692d0e4c591c5dd740b888eccab94ead..188408d04d94781fe3987bfdf7eaedcb3299827e 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -190,14 +190,26 @@ public class LayoutTransition { private long mChangingDisappearingStagger = 0; private long mChangingStagger = 0; + /** + * Static interpolators - these are stateless and can be shared across the instances + */ + private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR = + new AccelerateDecelerateInterpolator(); + private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator(); + private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR; + private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR; + private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR; + private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR; + private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR; + /** * The default interpolators used for the animations */ - private TimeInterpolator mAppearingInterpolator = new AccelerateDecelerateInterpolator(); - private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator(); - private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator(); - private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator(); - private TimeInterpolator mChangingInterpolator = new DecelerateInterpolator(); + private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator; + private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator; + private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator; + private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator; + private TimeInterpolator mChangingInterpolator = sChangingInterpolator; /** * These hashmaps are used to store the animations that are currently running as part of @@ -905,14 +917,24 @@ public class LayoutTransition { case APPEARING: startDelay = mChangingAppearingDelay + staggerDelay; staggerDelay += mChangingAppearingStagger; + if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) { + anim.setInterpolator(mChangingAppearingInterpolator); + } break; case DISAPPEARING: startDelay = mChangingDisappearingDelay + staggerDelay; staggerDelay += mChangingDisappearingStagger; + if (mChangingDisappearingInterpolator != + sChangingDisappearingInterpolator) { + anim.setInterpolator(mChangingDisappearingInterpolator); + } break; case CHANGING: startDelay = mChangingDelay + staggerDelay; staggerDelay += mChangingStagger; + if (mChangingInterpolator != sChangingInterpolator) { + anim.setInterpolator(mChangingInterpolator); + } break; } anim.setStartDelay(startDelay); @@ -1148,6 +1170,9 @@ public class LayoutTransition { anim.setTarget(child); anim.setStartDelay(mAppearingDelay); anim.setDuration(mAppearingDuration); + if (mAppearingInterpolator != sAppearingInterpolator) { + anim.setInterpolator(mAppearingInterpolator); + } if (anim instanceof ObjectAnimator) { ((ObjectAnimator) anim).setCurrentPlayTime(0); } @@ -1192,6 +1217,9 @@ public class LayoutTransition { Animator anim = mDisappearingAnim.clone(); anim.setStartDelay(mDisappearingDelay); anim.setDuration(mDisappearingDuration); + if (mDisappearingInterpolator != sDisappearingInterpolator) { + anim.setInterpolator(mDisappearingInterpolator); + } anim.setTarget(child); final float preAnimAlpha = child.getAlpha(); anim.addListener(new AnimatorListenerAdapter() { diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index 173ee73f337cd18aaa56b8c2f8a921068d09206a..9c88ccf32d412ef89ac0296ea27bd39bfa69e83e 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -123,9 +123,37 @@ public final class ObjectAnimator extends ValueAnimator { * in a call to the function setFoo() on the target object. If either * valueFrom or valueTo is null, then a getter function will * also be derived and called. + * + *

If this animator was created with a {@link Property} object instead of the + * string name of a property, then this method will return the {@link + * Property#getName() name} of that Property object instead. If this animator was + * created with one or more {@link PropertyValuesHolder} objects, then this method + * will return the {@link PropertyValuesHolder#getPropertyName() name} of that + * object (if there was just one) or a comma-separated list of all of the + * names (if there are more than one).

*/ public String getPropertyName() { - return mPropertyName; + String propertyName = null; + if (mPropertyName != null) { + propertyName = mPropertyName; + } else if (mProperty != null) { + propertyName = mProperty.getName(); + } else if (mValues != null && mValues.length > 0) { + for (int i = 0; i < mValues.length; ++i) { + if (i == 0) { + propertyName = ""; + } else { + propertyName += ","; + } + propertyName += mValues[i].getPropertyName(); + } + } + return propertyName; + } + + @Override + String getNameForTrace() { + return "animator:" + getPropertyName(); } /** diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index 5b1a7cf209ff0ab42441b83e390e486ff8938090..43014adc530037082780f5ca99c13a6fac680348 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -636,7 +636,7 @@ public class PropertyValuesHolder implements Cloneable { } /** - * The TypeEvaluator will the automatically determined based on the type of values + * The TypeEvaluator will be automatically determined based on the type of values * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so * desired. This may be important in cases where either the type of the values supplied * do not match the way that they should be interpolated between, or if the values diff --git a/core/java/android/animation/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java index e738da1130ff45e57763e6d7902d68ea0d6bf4a2..2640457c4b4a6960a94581c811d72c7b4b5337cb 100644 --- a/core/java/android/animation/TypeEvaluator.java +++ b/core/java/android/animation/TypeEvaluator.java @@ -19,7 +19,7 @@ package android.animation; /** * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators * allow developers to create animations on arbitrary property types, by allowing them to supply - * custom evaulators for types that are not automatically understood and used by the animation + * custom evaluators for types that are not automatically understood and used by the animation * system. * * @see ValueAnimator#setEvaluator(TypeEvaluator) @@ -41,4 +41,4 @@ public interface TypeEvaluator { */ public T evaluate(float fraction, T startValue, T endValue); -} \ No newline at end of file +} diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index cb4426491fbc96660d24c1230a36cb66fdf91c78..86da67383a27d797d302f2e06d64c79529026f24 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -80,6 +80,20 @@ public class ValueAnimator extends Animator { */ long mSeekTime = -1; + /** + * Set on the next frame after pause() is called, used to calculate a new startTime + * or delayStartTime which allows the animator to continue from the point at which + * it was paused. If negative, has not yet been set. + */ + private long mPauseTime; + + /** + * Set when an animator is resumed. This triggers logic in the next frame which + * actually resumes the animator. + */ + private boolean mResumed = false; + + // The static sAnimationHandler processes the internal timing loop on which all animations // are based /** @@ -147,7 +161,7 @@ public class ValueAnimator extends Animator { private boolean mStarted = false; /** - * Tracks whether we've notified listeners of the onAnimationSTart() event. This can be + * Tracks whether we've notified listeners of the onAnimationStart() event. This can be * complex to keep track of since we notify listeners at different times depending on * startDelay and whether start() was called before end(). */ @@ -869,7 +883,7 @@ public class ValueAnimator extends Animator { * *

If this ValueAnimator has only one set of values being animated between, this evaluator * will be used for that set. If there are several sets of values being animated, which is - * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator + * the case if PropertyValuesHolder objects were set on the ValueAnimator, then the evaluator * is assigned just to the first PropertyValuesHolder object.

* * @param value the evaluator to be used this animation @@ -914,6 +928,7 @@ public class ValueAnimator extends Animator { mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; + mPaused = false; AnimationHandler animationHandler = getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { @@ -970,6 +985,24 @@ public class ValueAnimator extends Animator { endAnimation(handler); } + @Override + public void resume() { + if (mPaused) { + mResumed = true; + } + super.resume(); + } + + @Override + public void pause() { + boolean previouslyPaused = mPaused; + super.pause(); + if (!previouslyPaused && mPaused) { + mPauseTime = -1; + mResumed = false; + } + } + @Override public boolean isRunning() { return (mPlayingState == RUNNING || mRunning); @@ -994,6 +1027,8 @@ public class ValueAnimator extends Animator { long currentPlayTime = currentTime - mStartTime; long timeLeft = mDuration - currentPlayTime; mStartTime = currentTime - timeLeft; + } else if (mStarted) { + end(); } else { start(true); } @@ -1008,6 +1043,7 @@ public class ValueAnimator extends Animator { handler.mPendingAnimations.remove(this); handler.mDelayedAnims.remove(this); mPlayingState = STOPPED; + mPaused = false; if ((mStarted || mRunning) && mListeners != null) { if (!mRunning) { // If it's not yet running, then start listeners weren't called. Call them now. @@ -1024,8 +1060,10 @@ public class ValueAnimator extends Animator { mStarted = false; mStartListenersCalled = false; mPlayingBackwards = false; - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "animator", - System.identityHashCode(this)); + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(), + System.identityHashCode(this)); + } } /** @@ -1033,8 +1071,10 @@ public class ValueAnimator extends Animator { * called on the UI thread. */ private void startAnimation(AnimationHandler handler) { - Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "animator", - System.identityHashCode(this)); + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(), + System.identityHashCode(this)); + } initAnimation(); handler.mAnimations.add(this); if (mStartDelay > 0 && mListeners != null) { @@ -1044,6 +1084,14 @@ public class ValueAnimator extends Animator { } } + /** + * Returns the name of this animator for debugging purposes. + */ + String getNameForTrace() { + return "animator"; + } + + /** * Internal function called to process an animation frame on an animation that is currently * sleeping through its startDelay phase. The return value indicates whether it @@ -1059,6 +1107,18 @@ public class ValueAnimator extends Animator { mStartedDelay = true; mDelayStartTime = currentTime; } else { + if (mPaused) { + if (mPauseTime < 0) { + mPauseTime = currentTime; + } + return false; + } else if (mResumed) { + mResumed = false; + if (mPauseTime > 0) { + // Offset by the duration that the animation was paused + mDelayStartTime += (currentTime - mPauseTime); + } + } long deltaTime = currentTime - mDelayStartTime; if (deltaTime > mStartDelay) { // startDelay ended - start the anim and record the @@ -1081,7 +1141,7 @@ public class ValueAnimator extends Animator { * * @param currentTime The current time, as tracked by the static timing handler * @return true if the animation's duration, including any repetitions due to - * repeatCount has been exceeded and the animation should be ended. + * repeatCount, has been exceeded and the animation should be ended. */ boolean animationFrame(long currentTime) { boolean done = false; @@ -1136,6 +1196,18 @@ public class ValueAnimator extends Animator { mSeekTime = -1; } } + if (mPaused) { + if (mPauseTime < 0) { + mPauseTime = frameTime; + } + return false; + } else if (mResumed) { + mResumed = false; + if (mPauseTime > 0) { + // Offset by the duration that the animation was paused + mStartTime += (frameTime - mPauseTime); + } + } // The frame time might be before the start time during the first frame of // an animation. The "current time" must always be on or after the start // time to avoid animating frames at negative time intervals. In practice, this diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 6b5df7f74c7046a63811326fbf6d9917f4b8b6c5..e29f8ea636478478f75b9cf40781853c1998b8b9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -16,6 +16,8 @@ package android.app; +import android.util.ArrayMap; +import android.util.SuperNotCalledException; import com.android.internal.app.ActionBarImpl; import com.android.internal.policy.PolicyManager; @@ -686,6 +688,7 @@ public class Activity extends ContextThemeWrapper boolean mFinished; boolean mStartedActivity; private boolean mDestroyed; + private boolean mDoReportFullyDrawn = true; /** true if the activity is going through a transient pause */ /*package*/ boolean mTemporaryPause = false; /** true if the activity is being destroyed in order to recreate it with a new configuration */ @@ -699,7 +702,7 @@ public class Activity extends ContextThemeWrapper Object activity; HashMap children; ArrayList fragments; - HashMap loaders; + ArrayMap loaders; } /* package */ NonConfigurationInstances mLastNonConfigurationInstances; @@ -724,7 +727,7 @@ public class Activity extends ContextThemeWrapper } }; - HashMap mAllLoaderManagers; + ArrayMap mAllLoaderManagers; LoaderManagerImpl mLoaderManager; private static final class ManagedCursor { @@ -744,6 +747,8 @@ public class Activity extends ContextThemeWrapper // protected by synchronized (this) int mResultCode = RESULT_CANCELED; Intent mResultData = null; + private TranslucentConversionListener mTranslucentCallback; + private boolean mChangeCanvasToTranslucent; private boolean mTitleReady = false; @@ -817,13 +822,13 @@ public class Activity extends ContextThemeWrapper return mLoaderManager; } mCheckedForLoaderManager = true; - mLoaderManager = getLoaderManager(null, mLoadersStarted, true); + mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true); return mLoaderManager; } LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) { if (mAllLoaderManagers == null) { - mAllLoaderManagers = new HashMap(); + mAllLoaderManagers = new ArrayMap(); } LoaderManagerImpl lm = mAllLoaderManagers.get(who); if (lm == null) { @@ -1034,7 +1039,7 @@ public class Activity extends ContextThemeWrapper if (mLoaderManager != null) { mLoaderManager.doStart(); } else if (!mCheckedForLoaderManager) { - mLoaderManager = getLoaderManager(null, mLoadersStarted, false); + mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false); } mCheckedForLoaderManager = true; } @@ -1381,6 +1386,7 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); getApplication().dispatchActivityStopped(this); + mTranslucentCallback = null; mCalled = true; } @@ -1448,6 +1454,29 @@ public class Activity extends ContextThemeWrapper getApplication().dispatchActivityDestroyed(this); } + /** + * Report to the system that your app is now fully drawn, purely for diagnostic + * purposes (calling it does not impact the visible behavior of the activity). + * This is only used to help instrument application launch times, so that the + * app can report when it is fully in a usable state; without this, the only thing + * the system itself can determine is the point at which the activity's window + * is first drawn and displayed. To participate in app launch time + * measurement, you should always call this method after first launch (when + * {@link #onCreate(android.os.Bundle)} is called), at the point where you have + * entirely drawn your UI and populated with all of the significant data. You + * can safely call this method any time after first launch as well, in which case + * it will simply be ignored. + */ + public void reportFullyDrawn() { + if (mDoReportFullyDrawn) { + mDoReportFullyDrawn = false; + try { + ActivityManagerNative.getDefault().reportActivityFullyDrawn(mToken); + } catch (RemoteException e) { + } + } + } + /** * Called by the system when the device configuration changes while your * activity is running. Note that this will only be called if @@ -1624,17 +1653,18 @@ public class Activity extends ContextThemeWrapper if (mAllLoaderManagers != null) { // prune out any loader managers that were already stopped and so // have nothing useful to retain. - LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()]; - mAllLoaderManagers.values().toArray(loaders); - if (loaders != null) { - for (int i=0; i=0; i--) { + loaders[i] = mAllLoaderManagers.valueAt(i); + } + for (int i=0; i + * Call this whenever the background of a translucent Activity has changed to become opaque. + * Doing so will allow the {@link android.view.Surface} of the Activity behind to be released. + *

+ * This call has no effect on non-translucent activities or on activities with the + * {@link android.R.attr#windowIsFloating} attribute. + * + * @see #convertToTranslucent(TranslucentConversionListener) + * @see TranslucentConversionListener + * + * @hide + */ + public void convertFromTranslucent() { + try { + mTranslucentCallback = null; + if (ActivityManagerNative.getDefault().convertFromTranslucent(mToken)) { + WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, true); + } + } catch (RemoteException e) { + // pass + } + } + + /** + * Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} back from + * opaque to translucent following a call to {@link #convertFromTranslucent()}. + *

+ * Calling this allows the Activity behind this one to be seen again. Once all such Activities + * have been redrawn {@link TranslucentConversionListener#onTranslucentConversionComplete} will + * be called indicating that it is safe to make this activity translucent again. Until + * {@link TranslucentConversionListener#onTranslucentConversionComplete} is called the image + * behind the frontmost Activity will be indeterminate. + *

+ * This call has no effect on non-translucent activities or on activities with the + * {@link android.R.attr#windowIsFloating} attribute. + * + * @param callback the method to call when all visible Activities behind this one have been + * drawn and it is safe to make this Activity translucent again. + * + * @see #convertFromTranslucent() + * @see TranslucentConversionListener + * + * @hide + */ + public void convertToTranslucent(TranslucentConversionListener callback) { + try { + mTranslucentCallback = callback; + mChangeCanvasToTranslucent = + ActivityManagerNative.getDefault().convertToTranslucent(mToken); + } catch (RemoteException e) { + // pass + } + } + + /** @hide */ + void onTranslucentConversionComplete(boolean drawComplete) { + if (mTranslucentCallback != null) { + mTranslucentCallback.onTranslucentConversionComplete(drawComplete); + mTranslucentCallback = null; + } + if (mChangeCanvasToTranslucent) { + WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false); + } + } + /** * Adjust the current immersive mode setting. * @@ -4903,6 +5010,7 @@ public class Activity extends ContextThemeWrapper * @return The new action mode, or null if the activity does not want to * provide special handling for this action mode. (It will be handled by the system.) */ + @Override public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { initActionBar(); if (mActionBar != null) { @@ -4917,6 +5025,7 @@ public class Activity extends ContextThemeWrapper * * @param mode The new action mode. */ + @Override public void onActionModeStarted(ActionMode mode) { } @@ -4926,6 +5035,7 @@ public class Activity extends ContextThemeWrapper * * @param mode The action mode that just finished. */ + @Override public void onActionModeFinished(ActionMode mode) { } @@ -5148,14 +5258,15 @@ public class Activity extends ContextThemeWrapper } mFragments.dispatchStart(); if (mAllLoaderManagers != null) { - LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()]; - mAllLoaderManagers.values().toArray(loaders); - if (loaders != null) { - for (int i=0; i=0; i--) { + loaders[i] = mAllLoaderManagers.valueAt(i); + } + for (int i=0; i{@code + * <meta-data>} name for a 'home' Activity that declares a package that is to be + * uninstalled in lieu of the declaring one. The package named here must be + * signed with the same certificate as the one declaring the {@code <meta-data>}. + */ + public static final String META_HOME_ALTERNATE = "android.app.home.alternate"; + /** * Result for IActivityManager.startActivity: an error where the * start had to be canceled. @@ -222,6 +232,56 @@ public class ActivityManager { /** @hide User operation call: given user id is the current user, can't be stopped. */ public static final int USER_OP_IS_CURRENT = -2; + /** @hide Process is a persistent system process. */ + public static final int PROCESS_STATE_PERSISTENT = 0; + + /** @hide Process is a persistent system process and is doing UI. */ + public static final int PROCESS_STATE_PERSISTENT_UI = 1; + + /** @hide Process is hosting the current top activities. Note that this covers + * all activities that are visible to the user. */ + public static final int PROCESS_STATE_TOP = 2; + + /** @hide Process is important to the user, and something they are aware of. */ + public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 3; + + /** @hide Process is important to the user, but not something they are aware of. */ + public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 4; + + /** @hide Process is in the background running a backup/restore operation. */ + public static final int PROCESS_STATE_BACKUP = 5; + + /** @hide Process is in the background, but it can't restore its state so we want + * to try to avoid killing it. */ + public static final int PROCESS_STATE_HEAVY_WEIGHT = 6; + + /** @hide Process is in the background running a service. Unlike oom_adj, this level + * is used for both the normal running in background state and the executing + * operations state. */ + public static final int PROCESS_STATE_SERVICE = 7; + + /** @hide Process is in the background running a receiver. Note that from the + * perspective of oom_adj receivers run at a higher foreground level, but for our + * prioritization here that is not necessary and putting them below services means + * many fewer changes in some process states as they receive broadcasts. */ + public static final int PROCESS_STATE_RECEIVER = 8; + + /** @hide Process is in the background but hosts the home activity. */ + public static final int PROCESS_STATE_HOME = 9; + + /** @hide Process is in the background but hosts the last shown activity. */ + public static final int PROCESS_STATE_LAST_ACTIVITY = 10; + + /** @hide Process is being cached for later use and contains activities. */ + public static final int PROCESS_STATE_CACHED_ACTIVITY = 11; + + /** @hide Process is being cached for later use and is a client of another cached + * process that contains activities. */ + public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 12; + + /** @hide Process is being cached for later use and is empty. */ + public static final int PROCESS_STATE_CACHED_EMPTY = 13; + /*package*/ ActivityManager(Context context, Handler handler) { mContext = context; mHandler = handler; @@ -370,53 +430,34 @@ public class ActivityManager { // Really brain dead right now -- just take this from the configured // vm heap size, and assume it is in megabytes and thus ends with "m". String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m"); - return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1)); + return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1)); } /** - * Used by persistent processes to determine if they are running on a - * higher-end device so should be okay using hardware drawing acceleration - * (which tends to consume a lot more RAM). - * @hide + * Returns true if this is a low-RAM device. Exactly whether a device is low-RAM + * is ultimately up to the device configuration, but currently it generally means + * something in the class of a 512MB device with about a 800x480 or less screen. + * This is mostly intended to be used by apps to determine whether they should turn + * off certain features that require more RAM. */ - static public boolean isHighEndGfx() { - MemInfoReader reader = new MemInfoReader(); - reader.readMemInfo(); - if (reader.getTotalSize() >= (512*1024*1024)) { - // If the device has at least 512MB RAM available to the kernel, - // we can afford the overhead of graphics acceleration. - return true; - } - - Display display = DisplayManagerGlobal.getInstance().getRealDisplay( - Display.DEFAULT_DISPLAY); - Point p = new Point(); - display.getRealSize(p); - int pixels = p.x * p.y; - if (pixels >= (1024*600)) { - // If this is a sufficiently large screen, then there are enough - // pixels on it that we'd really like to use hw drawing. - return true; - } - return false; + public boolean isLowRamDevice() { + return isLowRamDeviceStatic(); + } + + /** @hide */ + public static boolean isLowRamDeviceStatic() { + return "true".equals(SystemProperties.get("ro.config.low_ram", "false")); } /** - * Use to decide whether the running device can be considered a "large - * RAM" device. Exactly what memory limit large RAM is will vary, but - * it essentially means there is plenty of RAM to have lots of background - * processes running under decent loads. + * Used by persistent processes to determine if they are running on a + * higher-end device so should be okay using hardware drawing acceleration + * (which tends to consume a lot more RAM). * @hide */ - static public boolean isLargeRAM() { - MemInfoReader reader = new MemInfoReader(); - reader.readMemInfo(); - if (reader.getTotalSize() >= (640*1024*1024)) { - // Currently 640MB RAM available to the kernel is the point at - // which we have plenty of RAM to spare. - return true; - } - return false; + static public boolean isHighEndGfx() { + return !isLowRamDeviceStatic() && + !Resources.getSystem().getBoolean(com.android.internal.R.bool.config_avoidGfxAccel); } /** @@ -454,14 +495,22 @@ public class ActivityManager { * Description of the task's last state. */ public CharSequence description; - + + /** + * The id of the ActivityStack this Task was on most recently. + * @hide + */ + public int stackId; + public RecentTaskInfo() { } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeInt(persistentId); @@ -474,6 +523,7 @@ public class ActivityManager { ComponentName.writeToParcel(origActivity, dest); TextUtils.writeToParcel(description, dest, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + dest.writeInt(stackId); } public void readFromParcel(Parcel source) { @@ -486,8 +536,9 @@ public class ActivityManager { } origActivity = ComponentName.readFromParcel(source); description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + stackId = source.readInt(); } - + public static final Creator CREATOR = new Creator() { public RecentTaskInfo createFromParcel(Parcel source) { @@ -629,6 +680,12 @@ public class ActivityManager { */ public int numRunning; + /** + * Last time task was run. For sorting. + * @hide + */ + public long lastActiveTime; + public RunningTaskInfo() { } @@ -1230,7 +1287,169 @@ public class ActivityManager { } catch (RemoteException e) { } } - + + /** + * Information you can retrieve about the WindowManager StackBox hierarchy. + * @hide + */ + public static class StackBoxInfo implements Parcelable { + public int stackBoxId; + public float weight; + public boolean vertical; + public Rect bounds; + public StackBoxInfo[] children; + public int stackId; + public StackInfo stack; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(stackBoxId); + dest.writeFloat(weight); + dest.writeInt(vertical ? 1 : 0); + bounds.writeToParcel(dest, flags); + dest.writeInt(stackId); + if (children != null) { + children[0].writeToParcel(dest, flags); + children[1].writeToParcel(dest, flags); + } else { + stack.writeToParcel(dest, flags); + } + } + + public void readFromParcel(Parcel source) { + stackBoxId = source.readInt(); + weight = source.readFloat(); + vertical = source.readInt() == 1; + bounds = Rect.CREATOR.createFromParcel(source); + stackId = source.readInt(); + if (stackId == -1) { + children = new StackBoxInfo[2]; + children[0] = StackBoxInfo.CREATOR.createFromParcel(source); + children[1] = StackBoxInfo.CREATOR.createFromParcel(source); + } else { + stack = StackInfo.CREATOR.createFromParcel(source); + } + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public StackBoxInfo createFromParcel(Parcel source) { + return new StackBoxInfo(source); + } + + @Override + public StackBoxInfo[] newArray(int size) { + return new StackBoxInfo[size]; + } + }; + + public StackBoxInfo() { + } + + public StackBoxInfo(Parcel source) { + readFromParcel(source); + } + + public String toString(String prefix) { + StringBuilder sb = new StringBuilder(256); + sb.append(prefix); sb.append("Box id=" + stackBoxId); sb.append(" weight=" + weight); + sb.append(" vertical=" + vertical); sb.append(" bounds=" + bounds.toShortString()); + sb.append("\n"); + if (children != null) { + sb.append(prefix); sb.append("First child=\n"); + sb.append(children[0].toString(prefix + " ")); + sb.append(prefix); sb.append("Second child=\n"); + sb.append(children[1].toString(prefix + " ")); + } else { + sb.append(prefix); sb.append("Stack=\n"); + sb.append(stack.toString(prefix + " ")); + } + return sb.toString(); + } + + @Override + public String toString() { + return toString(""); + } + } + + /** + * Information you can retrieve about an ActivityStack in the system. + * @hide + */ + public static class StackInfo implements Parcelable { + public int stackId; + public Rect bounds; + public int[] taskIds; + public String[] taskNames; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(stackId); + dest.writeInt(bounds.left); + dest.writeInt(bounds.top); + dest.writeInt(bounds.right); + dest.writeInt(bounds.bottom); + dest.writeIntArray(taskIds); + dest.writeStringArray(taskNames); + } + + public void readFromParcel(Parcel source) { + stackId = source.readInt(); + bounds = new Rect( + source.readInt(), source.readInt(), source.readInt(), source.readInt()); + taskIds = source.createIntArray(); + taskNames = source.createStringArray(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public StackInfo createFromParcel(Parcel source) { + return new StackInfo(source); + } + @Override + public StackInfo[] newArray(int size) { + return new StackInfo[size]; + } + }; + + public StackInfo() { + } + + private StackInfo(Parcel source) { + readFromParcel(source); + } + + public String toString(String prefix) { + StringBuilder sb = new StringBuilder(256); + sb.append(prefix); sb.append("Stack id="); sb.append(stackId); + sb.append(" bounds="); sb.append(bounds.toShortString()); sb.append("\n"); + prefix = prefix + " "; + for (int i = 0; i < taskIds.length; ++i) { + sb.append(prefix); sb.append("taskId="); sb.append(taskIds[i]); + sb.append(": "); sb.append(taskNames[i]); sb.append("\n"); + } + return sb.toString(); + } + + @Override + public String toString() { + return toString(""); + } + } + /** * @hide */ @@ -1242,7 +1461,21 @@ public class ActivityManager { return false; } } - + + /** + * Permits an application to erase its own data from disk. This is equivalent to + * the user choosing to clear the app's data from within the device settings UI. It + * erases all dynamic data associated with the app -- its private data and data in its + * private area on external storage -- but does not remove the installed application + * itself, nor any OBB files. + * + * @return {@code true} if the application successfully requested that the application's + * data be erased; {@code false} otherwise. + */ + public boolean clearApplicationUserData() { + return clearApplicationUserData(mContext.getPackageName(), null); + } + /** * Information you can retrieve about any processes that are in an error condition. */ @@ -1304,10 +1537,12 @@ public class ActivityManager { public ProcessErrorStateInfo() { } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(condition); dest.writeString(processName); @@ -1318,7 +1553,7 @@ public class ActivityManager { dest.writeString(longMsg); dest.writeString(stackTrace); } - + public void readFromParcel(Parcel source) { condition = source.readInt(); processName = source.readString(); @@ -1538,7 +1773,7 @@ public class ActivityManager { public ComponentName importanceReasonComponent; /** - * When {@link importanceReasonPid} is non-0, this is the importance + * When {@link #importanceReasonPid} is non-0, this is the importance * of the other pid. @hide */ public int importanceReasonImportance; @@ -1888,7 +2123,12 @@ public class ActivityManager { } // If the target is not exported, then nobody else can get to it. if (!exported) { - Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid); + /* + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid, + here); + */ return PackageManager.PERMISSION_DENIED; } if (permission == null) { @@ -2010,4 +2250,60 @@ public class ActivityManager { return false; } } + + /** + * Perform a system dump of various state associated with the given application + * package name. This call blocks while the dump is being performed, so should + * not be done on a UI thread. The data will be written to the given file + * descriptor as text. An application must hold the + * {@link android.Manifest.permission#DUMP} permission to make this call. + * @param fd The file descriptor that the dump should be written to. The file + * descriptor is not closed by this function; the caller continues to + * own it. + * @param packageName The name of the package that is to be dumped. + */ + public void dumpPackageState(FileDescriptor fd, String packageName) { + dumpPackageStateStatic(fd, packageName); + } + + /** + * @hide + */ + public static void dumpPackageStateStatic(FileDescriptor fd, String packageName) { + FileOutputStream fout = new FileOutputStream(fd); + PrintWriter pw = new FastPrintWriter(fout); + dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] { "package", packageName }); + pw.println(); + dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName }); + pw.println(); + dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName }); + pw.println(); + dumpService(pw, fd, "package", new String[] { packageName }); + pw.println(); + dumpService(pw, fd, BatteryStats.SERVICE_NAME, new String[] { packageName }); + pw.flush(); + } + + private static void dumpService(PrintWriter pw, FileDescriptor fd, String name, String[] args) { + pw.print("DUMP OF SERVICE "); pw.print(name); pw.println(":"); + IBinder service = ServiceManager.checkService(name); + if (service == null) { + pw.println(" (Service not found)"); + return; + } + TransferPipe tp = null; + try { + pw.flush(); + tp = new TransferPipe(); + tp.setBufferPrefix(" "); + service.dump(tp.getWriteFd().getFileDescriptor(), args); + tp.go(fd); + } catch (Throwable e) { + if (tp != null) { + tp.kill(); + } + pw.println("Failure dumping service:"); + e.printStackTrace(pw); + } + } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index d4478bf046d6d4c8f55d0ab42de6a696e8457172..74266ccf58a23e6b3f608a5d58b4d4b295a0e868 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -16,15 +16,18 @@ package android.app; +import android.app.ActivityManager.StackBoxInfo; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.UriPermission; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; +import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -107,7 +110,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM public ActivityManagerNative() { attachInterface(this, descriptor); } - + + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { @@ -150,7 +154,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM int startFlags = data.readInt(); String profileFile = data.readString(); ParcelFileDescriptor profileFd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; Bundle options = data.readInt() != 0 ? Bundle.CREATOR.createFromParcel(data) : null; int userId = data.readInt(); @@ -176,7 +180,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM int startFlags = data.readInt(); String profileFile = data.readString(); ParcelFileDescriptor profileFd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; Bundle options = data.readInt() != 0 ? Bundle.CREATOR.createFromParcel(data) : null; int userId = data.readInt(); @@ -197,7 +201,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM Intent intent = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); IBinder resultTo = data.readStrongBinder(); - String resultWho = data.readString(); + String resultWho = data.readString(); int requestCode = data.readInt(); int startFlags = data.readInt(); Configuration config = Configuration.CREATOR.createFromParcel(data); @@ -223,7 +227,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM } String resolvedType = data.readString(); IBinder resultTo = data.readStrongBinder(); - String resultWho = data.readString(); + String resultWho = data.readString(); int requestCode = data.readInt(); int flagsMask = data.readInt(); int flagsValues = data.readInt(); @@ -236,7 +240,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeInt(result); return true; } - + case START_NEXT_MATCHING_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); @@ -267,7 +271,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case FINISH_SUB_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); - String resultWho = data.readString(); + String resultWho = data.readString(); int requestCode = data.readInt(); finishSubActivity(token, resultWho, requestCode); reply.writeNoException(); @@ -478,14 +482,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IThumbnailReceiver receiver = receiverBinder != null ? IThumbnailReceiver.Stub.asInterface(receiverBinder) : null; - List list = getTasks(maxNum, fl, receiver); + List list = getTasks(maxNum, fl, receiver); reply.writeNoException(); int N = list != null ? list.size() : -1; reply.writeInt(N); int i; for (i=0; i list = getServices(maxNum, fl); reply.writeNoException(); int N = list != null ? list.size() : -1; reply.writeInt(N); int i; for (i=0; i list = getRunningAppProcesses(); @@ -609,6 +611,67 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case CREATE_STACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int taskId = data.readInt(); + int relativeStackId = data.readInt(); + int position = data.readInt(); + float weight = data.readFloat(); + int res = createStack(taskId, relativeStackId, position, weight); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case MOVE_TASK_TO_STACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int taskId = data.readInt(); + int stackId = data.readInt(); + boolean toTop = data.readInt() != 0; + moveTaskToStack(taskId, stackId, toTop); + reply.writeNoException(); + return true; + } + + case RESIZE_STACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int stackBoxId = data.readInt(); + float weight = data.readFloat(); + resizeStackBox(stackBoxId, weight); + reply.writeNoException(); + return true; + } + + case GET_STACK_BOXES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + List list = getStackBoxes(); + reply.writeNoException(); + reply.writeTypedList(list); + return true; + } + + case GET_STACK_BOX_INFO_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int stackBoxId = data.readInt(); + StackBoxInfo info = getStackBoxInfo(stackBoxId); + reply.writeNoException(); + if (info != null) { + reply.writeInt(1); + info.writeToParcel(reply, 0); + } else { + reply.writeInt(0); + } + return true; + } + + case SET_FOCUSED_STACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int stackId = data.readInt(); + setFocusedStack(stackId); + reply.writeNoException(); + return true; + } + case GET_TASK_FOR_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -695,6 +758,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + appNotRespondingViaProvider(b); + reply.writeNoException(); + return true; + } + case REMOVE_CONTENT_PROVIDER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); @@ -1033,9 +1104,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeInt(res); return true; } - + case CLEAR_APP_DATA_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); + data.enforceInterface(IActivityManager.descriptor); String packageName = data.readString(); IPackageDataObserver observer = IPackageDataObserver.Stub.asInterface( data.readStrongBinder()); @@ -1045,7 +1116,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeInt(res ? 1 : 0); return true; } - + case GRANT_URI_PERMISSION_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); @@ -1057,7 +1128,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } - + case REVOKE_URI_PERMISSION_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); @@ -1068,7 +1139,36 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } - + + case TAKE_PERSISTABLE_URI_PERMISSION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Uri uri = Uri.CREATOR.createFromParcel(data); + int mode = data.readInt(); + takePersistableUriPermission(uri, mode); + reply.writeNoException(); + return true; + } + + case RELEASE_PERSISTABLE_URI_PERMISSION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Uri uri = Uri.CREATOR.createFromParcel(data); + int mode = data.readInt(); + releasePersistableUriPermission(uri, mode); + reply.writeNoException(); + return true; + } + + case GET_PERSISTED_URI_PERMISSIONS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + final String packageName = data.readString(); + final boolean incoming = data.readInt() != 0; + final ParceledListSlice perms = getPersistedUriPermissions( + packageName, incoming); + reply.writeNoException(); + perms.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } + case SHOW_WAITING_FOR_DEBUGGER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); @@ -1257,7 +1357,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } - + case FORCE_STOP_PACKAGE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String packageName = data.readString(); @@ -1293,7 +1393,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM int profileType = data.readInt(); String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; boolean res = profileControl(process, userId, start, path, fd, profileType); reply.writeNoException(); reply.writeInt(res ? 1 : 0); @@ -1363,7 +1463,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); String pkg = data.readString(); int appid = data.readInt(); - killApplicationWithAppId(pkg, appid); + String reason = data.readString(); + killApplicationWithAppId(pkg, appid, reason); reply.writeNoException(); return true; } @@ -1437,6 +1538,24 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case CONVERT_FROM_TRANSLUCENT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + boolean converted = convertFromTranslucent(token); + reply.writeNoException(); + reply.writeInt(converted ? 1 : 0); + return true; + } + + case CONVERT_TO_TRANSLUCENT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + boolean converted = convertToTranslucent(token); + reply.writeNoException(); + reply.writeInt(converted ? 1 : 0); + return true; + } + case SET_IMMERSIVE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -1528,7 +1647,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM boolean managed = data.readInt() != 0; String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; boolean res = dumpHeap(process, userId, managed, path, fd); reply.writeNoException(); reply.writeInt(res ? 1 : 0); @@ -1837,26 +1956,27 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); int pid = data.readInt(); boolean aboveSystem = data.readInt() != 0; - long res = inputDispatchingTimedOut(pid, aboveSystem); + String reason = data.readString(); + long res = inputDispatchingTimedOut(pid, aboveSystem, reason); reply.writeNoException(); reply.writeLong(res); return true; } - case GET_TOP_ACTIVITY_EXTRAS_TRANSACTION: { + case GET_ASSIST_CONTEXT_EXTRAS_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int requestType = data.readInt(); - Bundle res = getTopActivityExtras(requestType); + Bundle res = getAssistContextExtras(requestType); reply.writeNoException(); reply.writeBundle(res); return true; } - case REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION: { + case REPORT_ASSIST_CONTEXT_EXTRAS_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); Bundle extras = data.readBundle(); - reportTopActivityExtras(token, extras); + reportAssistContextExtras(token, extras); reply.writeNoException(); return true; } @@ -1879,6 +1999,35 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case REPORT_ACTIVITY_FULLY_DRAWN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + reportActivityFullyDrawn(token); + reply.writeNoException(); + return true; + } + + case NOTIFY_ACTIVITY_DRAWN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + notifyActivityDrawn(token); + reply.writeNoException(); + return true; + } + + case RESTART_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + restart(); + reply.writeNoException(); + return true; + } + + case PERFORM_IDLE_MAINTENANCE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + performIdleMaintenance(); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -2565,6 +2714,94 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } + @Override + public int createStack(int taskId, int relativeStackBoxId, int position, float weight) + throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(taskId); + data.writeInt(relativeStackBoxId); + data.writeInt(position); + data.writeFloat(weight); + mRemote.transact(CREATE_STACK_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + @Override + public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(taskId); + data.writeInt(stackId); + data.writeInt(toTop ? 1 : 0); + mRemote.transact(MOVE_TASK_TO_STACK_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + @Override + public void resizeStackBox(int stackBoxId, float weight) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(stackBoxId); + data.writeFloat(weight); + mRemote.transact(RESIZE_STACK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + @Override + public List getStackBoxes() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_STACK_BOXES_TRANSACTION, data, reply, 0); + reply.readException(); + ArrayList list = reply.createTypedArrayList(StackBoxInfo.CREATOR); + data.recycle(); + reply.recycle(); + return list; + } + @Override + public StackBoxInfo getStackBoxInfo(int stackBoxId) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(stackBoxId); + mRemote.transact(GET_STACK_BOX_INFO_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + StackBoxInfo info = null; + if (res != 0) { + info = StackBoxInfo.CREATOR.createFromParcel(reply); + } + data.recycle(); + reply.recycle(); + return info; + } + @Override + public void setFocusedStack(int stackId) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(stackId); + mRemote.transact(SET_FOCUSED_STACK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException { Parcel data = Parcel.obtain(); @@ -2665,6 +2902,7 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } + public void unstableProviderDied(IBinder connection) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2676,6 +2914,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + @Override + public void appNotRespondingViaProvider(IBinder connection) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(connection); + mRemote.transact(APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -3173,7 +3423,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(packageName); - data.writeStrongBinder(observer.asBinder()); + data.writeStrongBinder((observer != null) ? observer.asBinder() : null); data.writeInt(userId); mRemote.transact(CLEAR_APP_DATA_TRANSACTION, data, reply, 0); reply.readException(); @@ -3225,6 +3475,50 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } + + @Override + public void takePersistableUriPermission(Uri uri, int mode) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + uri.writeToParcel(data, 0); + data.writeInt(mode); + mRemote.transact(TAKE_PERSISTABLE_URI_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override + public void releasePersistableUriPermission(Uri uri, int mode) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + uri.writeToParcel(data, 0); + data.writeInt(mode); + mRemote.transact(RELEASE_PERSISTABLE_URI_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override + public ParceledListSlice getPersistedUriPermissions( + String packageName, boolean incoming) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + data.writeInt(incoming ? 1 : 0); + mRemote.transact(GET_PERSISTED_URI_PERMISSIONS_TRANSACTION, data, reply, 0); + reply.readException(); + final ParceledListSlice perms = ParceledListSlice.CREATOR.createFromParcel( + reply); + data.recycle(); + reply.recycle(); + return perms; + } + public void showWaitingForDebugger(IApplicationThread who, boolean waiting) throws RemoteException { Parcel data = Parcel.obtain(); @@ -3575,12 +3869,14 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); } - public void killApplicationWithAppId(String pkg, int appid) throws RemoteException { + public void killApplicationWithAppId(String pkg, int appid, String reason) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(pkg); data.writeInt(appid); + data.writeString(reason); mRemote.transact(KILL_APPLICATION_WITH_APPID_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -3671,7 +3967,35 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - + + public boolean convertFromTranslucent(IBinder token) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(CONVERT_FROM_TRANSLUCENT_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + + public boolean convertToTranslucent(IBinder token) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(CONVERT_TO_TRANSLUCENT_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public void setImmersive(IBinder token, boolean immersive) throws RemoteException { Parcel data = Parcel.obtain(); @@ -4228,12 +4552,14 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public long inputDispatchingTimedOut(int pid, boolean aboveSystem) throws RemoteException { + public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(pid); data.writeInt(aboveSystem ? 1 : 0); + data.writeString(reason); mRemote.transact(INPUT_DISPATCHING_TIMED_OUT_TRANSACTION, data, reply, 0); reply.readException(); long res = reply.readInt(); @@ -4242,12 +4568,12 @@ class ActivityManagerProxy implements IActivityManager return res; } - public Bundle getTopActivityExtras(int requestType) throws RemoteException { + public Bundle getAssistContextExtras(int requestType) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(requestType); - mRemote.transact(GET_TOP_ACTIVITY_EXTRAS_TRANSACTION, data, reply, 0); + mRemote.transact(GET_ASSIST_CONTEXT_EXTRAS_TRANSACTION, data, reply, 0); reply.readException(); Bundle res = reply.readBundle(); data.recycle(); @@ -4255,13 +4581,14 @@ class ActivityManagerProxy implements IActivityManager return res; } - public void reportTopActivityExtras(IBinder token, Bundle extras) throws RemoteException { + public void reportAssistContextExtras(IBinder token, Bundle extras) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); data.writeBundle(extras); - mRemote.transact(REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION, data, reply, 0); + mRemote.transact(REPORT_ASSIST_CONTEXT_EXTRAS_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -4291,5 +4618,47 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public void reportActivityFullyDrawn(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(REPORT_ACTIVITY_FULLY_DRAWN_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void notifyActivityDrawn(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(NOTIFY_ACTIVITY_DRAWN_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void restart() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(RESTART_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void performIdleMaintenance() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(PERFORM_IDLE_MAINTENANCE_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4268fa6211ae7b17cd8f563a128e7d92091ab5f1..df63ab342dfe0d12008a2dc3616b0fc3f07a3709 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -69,13 +69,14 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.LogPrinter; import android.util.PrintWriterPrinter; import android.util.Slog; -import android.view.CompatibilityInfoHolder; +import android.util.SuperNotCalledException; import android.view.Display; import android.view.HardwareRenderer; import android.view.View; @@ -91,8 +92,10 @@ import android.security.AndroidKeyStoreProvider; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.os.SamplingProfilerIntegration; +import com.android.internal.util.FastPrintWriter; import com.android.internal.util.Objects; import com.android.org.conscrypt.OpenSSLSocketImpl; +import com.google.android.collect.Lists; import java.io.File; import java.io.FileDescriptor; @@ -103,8 +106,6 @@ import java.lang.ref.WeakReference; import java.net.InetAddress; import java.security.Security; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -117,12 +118,6 @@ import libcore.io.IoUtils; import dalvik.system.CloseGuard; -final class SuperNotCalledException extends AndroidRuntimeException { - public SuperNotCalledException(String msg) { - super(msg); - } -} - final class RemoteServiceException extends AndroidRuntimeException { public RemoteServiceException(String msg) { super(msg); @@ -147,7 +142,7 @@ public final class ActivityThread { public static final boolean DEBUG_BROADCAST = false; private static final boolean DEBUG_RESULTS = false; private static final boolean DEBUG_BACKUP = false; - private static final boolean DEBUG_CONFIGURATION = false; + public static final boolean DEBUG_CONFIGURATION = false; private static final boolean DEBUG_SERVICE = false; private static final boolean DEBUG_MEMORY_TRIM = false; private static final boolean DEBUG_PROVIDER = false; @@ -164,28 +159,26 @@ public final class ActivityThread { final ApplicationThread mAppThread = new ApplicationThread(); final Looper mLooper = Looper.myLooper(); final H mH = new H(); - final HashMap mActivities - = new HashMap(); + final ArrayMap mActivities + = new ArrayMap(); // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. ActivityClientRecord mNewActivities = null; // Number of activities that are currently visible on-screen. int mNumVisibleActivities = 0; - final HashMap mServices - = new HashMap(); + final ArrayMap mServices + = new ArrayMap(); AppBindData mBoundApplication; Profiler mProfiler; int mCurDefaultDisplayDpi; boolean mDensityCompatMode; Configuration mConfiguration; Configuration mCompatConfiguration; - Configuration mResConfiguration; - CompatibilityInfo mResCompatibilityInfo; Application mInitialApplication; final ArrayList mAllApplications = new ArrayList(); // set of instantiated backup agents, keyed by package name - final HashMap mBackupAgents = new HashMap(); + final ArrayMap mBackupAgents = new ArrayMap(); /** Reference to singleton {@link ActivityThread} */ private static ActivityThread sCurrentActivityThread; Instrumentation mInstrumentation; @@ -205,18 +198,16 @@ public final class ActivityThread { // which means this lock gets held while the activity and window managers // holds their own lock. Thus you MUST NEVER call back into the activity manager // or window manager or anything that depends on them while holding this lock. - final HashMap> mPackages - = new HashMap>(); - final HashMap> mResourcePackages - = new HashMap>(); - final HashMap mDefaultDisplayMetrics - = new HashMap(); - final HashMap > mActiveResources - = new HashMap >(); + final ArrayMap> mPackages + = new ArrayMap>(); + final ArrayMap> mResourcePackages + = new ArrayMap>(); final ArrayList mRelaunchingActivities = new ArrayList(); Configuration mPendingConfiguration = null; + private final ResourcesManager mResourcesManager; + private static final class ProviderKey { final String authority; final int userId; @@ -242,17 +233,17 @@ public final class ActivityThread { } // The lock of mProviderMap protects the following variables. - final HashMap mProviderMap - = new HashMap(); - final HashMap mProviderRefCountMap - = new HashMap(); - final HashMap mLocalProviders - = new HashMap(); - final HashMap mLocalProvidersByName - = new HashMap(); - - final HashMap> mOnPauseListeners - = new HashMap>(); + final ArrayMap mProviderMap + = new ArrayMap(); + final ArrayMap mProviderRefCountMap + = new ArrayMap(); + final ArrayMap mLocalProviders + = new ArrayMap(); + final ArrayMap mLocalProvidersByName + = new ArrayMap(); + + final ArrayMap> mOnPauseListeners + = new ArrayMap>(); final GcIdler mGcIdler = new GcIdler(); boolean mGcIdlerScheduled = false; @@ -534,25 +525,30 @@ public final class ActivityThread { CompatibilityInfo info; } - static final class RequestActivityExtras { + static final class RequestAssistContextExtras { IBinder activityToken; IBinder requestToken; int requestType; } - + private native void dumpGraphicsInfo(FileDescriptor fd); private class ApplicationThread extends ApplicationThreadNative { - private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s"; + private static final String HEAP_FULL_COLUMN + = "%13s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s"; + private static final String HEAP_COLUMN + = "%13s %8s %8s %8s %8s %8s %8s %8s"; private static final String ONE_COUNT_COLUMN = "%21s %8d"; private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d"; private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; // Formatting for checkin service - update version if row format changes - private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1; + private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 3; + + private int mLastProcessState = -1; private void updatePendingConfiguration(Configuration config) { - synchronized (mPackages) { + synchronized (mResourcesManager) { if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(config)) { mPendingConfiguration = config; @@ -586,7 +582,9 @@ public final class ActivityThread { queueOrSendMessage(H.SLEEPING, token, sleeping ? 1 : 0); } - public final void scheduleResumeActivity(IBinder token, boolean isForward) { + public final void scheduleResumeActivity(IBinder token, int processState, + boolean isForward) { + updateProcessState(processState, false); queueOrSendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0); } @@ -601,9 +599,12 @@ public final class ActivityThread { // activity itself back to the activity manager. (matters more with ipc) public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, - Bundle state, List pendingResults, + int procState, Bundle state, List pendingResults, List pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + + updateProcessState(procState, false); + ActivityClientRecord r = new ActivityClientRecord(); r.token = token; @@ -651,7 +652,8 @@ public final class ActivityThread { public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, - boolean sync, int sendingUser) { + boolean sync, int sendingUser, int processState) { + updateProcessState(processState, false); ReceiverData r = new ReceiverData(intent, resultCode, data, extras, sync, false, mAppThread.asBinder(), sendingUser); r.info = info; @@ -679,7 +681,8 @@ public final class ActivityThread { } public final void scheduleCreateService(IBinder token, - ServiceInfo info, CompatibilityInfo compatInfo) { + ServiceInfo info, CompatibilityInfo compatInfo, int processState) { + updateProcessState(processState, false); CreateServiceData s = new CreateServiceData(); s.token = token; s.info = info; @@ -689,7 +692,8 @@ public final class ActivityThread { } public final void scheduleBindService(IBinder token, Intent intent, - boolean rebind) { + boolean rebind, int processState) { + updateProcessState(processState, false); BindServiceData s = new BindServiceData(); s.token = token; s.intent = intent; @@ -788,8 +792,8 @@ public final class ActivityThread { InetAddress.clearDnsCache(); } - public void setHttpProxy(String host, String port, String exclList) { - Proxy.setHttpProxySystemProperty(host, port, exclList); + public void setHttpProxy(String host, String port, String exclList, String pacFileUrl) { + Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } public void processInBackground() { @@ -814,7 +818,8 @@ public final class ActivityThread { // applies transaction ordering per object for such calls. public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode, String dataStr, Bundle extras, boolean ordered, - boolean sticky, int sendingUser) throws RemoteException { + boolean sticky, int sendingUser, int processState) throws RemoteException { + updateProcessState(processState, false); receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky, sendingUser); } @@ -854,10 +859,6 @@ public final class ActivityThread { } } - public void getMemoryInfo(Debug.MemoryInfo outInfo) { - Debug.getMemoryInfo(outInfo); - } - public void dispatchPackageBroadcast(int cmd, String[] packages) { queueOrSendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd); } @@ -894,29 +895,23 @@ public final class ActivityThread { } @Override - public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, - boolean all, String[] args) { + public void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin, + boolean dumpFullInfo, boolean dumpDalvik, String[] args) { FileOutputStream fout = new FileOutputStream(fd); - PrintWriter pw = new PrintWriter(fout); + PrintWriter pw = new FastPrintWriter(fout); try { - return dumpMemInfo(pw, checkin, all); + dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik); } finally { pw.flush(); } } - private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all) { + private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, + boolean dumpFullInfo, boolean dumpDalvik) { long nativeMax = Debug.getNativeHeapSize() / 1024; long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; long nativeFree = Debug.getNativeHeapFreeSize() / 1024; - Debug.MemoryInfo memInfo = new Debug.MemoryInfo(); - Debug.getMemoryInfo(memInfo); - - if (!all) { - return memInfo; - } - Runtime runtime = Runtime.getRuntime(); long dalvikMax = runtime.totalMemory() / 1024; @@ -968,21 +963,48 @@ public final class ActivityThread { pw.print(memInfo.nativePss); pw.print(','); pw.print(memInfo.dalvikPss); pw.print(','); pw.print(memInfo.otherPss); pw.print(','); - pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(','); + pw.print(memInfo.getTotalPss()); pw.print(','); + + // Heap info - swappable set size + pw.print(memInfo.nativeSwappablePss); pw.print(','); + pw.print(memInfo.dalvikSwappablePss); pw.print(','); + pw.print(memInfo.otherSwappablePss); pw.print(','); + pw.print(memInfo.getTotalSwappablePss()); pw.print(','); - // Heap info - shared + // Heap info - shared dirty pw.print(memInfo.nativeSharedDirty); pw.print(','); pw.print(memInfo.dalvikSharedDirty); pw.print(','); pw.print(memInfo.otherSharedDirty); pw.print(','); - pw.print(memInfo.nativeSharedDirty + memInfo.dalvikSharedDirty - + memInfo.otherSharedDirty); pw.print(','); + pw.print(memInfo.getTotalSharedDirty()); pw.print(','); - // Heap info - private + // Heap info - shared clean + pw.print(memInfo.nativeSharedClean); pw.print(','); + pw.print(memInfo.dalvikSharedClean); pw.print(','); + pw.print(memInfo.otherSharedClean); pw.print(','); + pw.print(memInfo.getTotalSharedClean()); pw.print(','); + + // Heap info - private Dirty pw.print(memInfo.nativePrivateDirty); pw.print(','); pw.print(memInfo.dalvikPrivateDirty); pw.print(','); pw.print(memInfo.otherPrivateDirty); pw.print(','); - pw.print(memInfo.nativePrivateDirty + memInfo.dalvikPrivateDirty - + memInfo.otherPrivateDirty); pw.print(','); + pw.print(memInfo.getTotalPrivateDirty()); pw.print(','); + + // Heap info - private Clean + pw.print(memInfo.nativePrivateClean); pw.print(','); + pw.print(memInfo.dalvikPrivateClean); pw.print(','); + pw.print(memInfo.otherPrivateClean); pw.print(','); + pw.print(memInfo.getTotalPrivateClean()); pw.print(','); + + // Heap info - other areas + for (int i=0; i wr = mActiveResources.get(key); - r = wr != null ? wr.get() : null; - //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); - if (r != null && r.getAssets().isUpToDate()) { - if (false) { - Slog.w(TAG, "Returning cached resources " + r + " " + resDir - + ": appScale=" + r.getCompatibilityInfo().applicationScale); - } - return r; - } - } - - //if (r != null) { - // Slog.w(TAG, "Throwing away out-of-date resources!!!! " - // + r + " " + resDir); - //} - - AssetManager assets = new AssetManager(); - if (assets.addAssetPath(resDir) == 0) { - return null; - } - - //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); - DisplayMetrics dm = getDisplayMetricsLocked(displayId, null); - Configuration config; - boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); - if (!isDefaultDisplay || key.mOverrideConfiguration != null) { - config = new Configuration(getConfiguration()); - if (!isDefaultDisplay) { - applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); - } - if (key.mOverrideConfiguration != null) { - config.updateFrom(key.mOverrideConfiguration); - } - } else { - config = getConfiguration(); - } - r = new Resources(assets, dm, config, compInfo); - if (false) { - Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " - + r.getConfiguration() + " appScale=" - + r.getCompatibilityInfo().applicationScale); - } - - synchronized (mPackages) { - WeakReference wr = mActiveResources.get(key); - Resources existing = wr != null ? wr.get() : null; - if (existing != null && existing.getAssets().isUpToDate()) { - // Someone else already created the resources while we were - // unlocked; go ahead and use theirs. - r.getAssets().close(); - return existing; - } - - // XXX need to remove entries when weak references go away - mActiveResources.put(key, new WeakReference(r)); - return r; - } - } - /** * Creates the top level resources for the given package. */ Resources getTopLevelResources(String resDir, int displayId, Configuration overrideConfiguration, LoadedApk pkgInfo) { - return getTopLevelResources(resDir, displayId, overrideConfiguration, - pkgInfo.mCompatibilityInfo.get()); + return mResourcesManager.getTopLevelResources(resDir, displayId, overrideConfiguration, + pkgInfo.getCompatibilityInfo(), null); } final Handler getHandler() { @@ -1776,7 +1747,7 @@ public final class ActivityThread { public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, int flags, int userId) { - synchronized (mPackages) { + synchronized (mResourcesManager) { WeakReference ref; if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) { ref = mPackages.get(packageName); @@ -1846,7 +1817,7 @@ public final class ActivityThread { } public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { - synchronized (mPackages) { + synchronized (mResourcesManager) { WeakReference ref; if (includeCode) { ref = mPackages.get(packageName); @@ -1859,7 +1830,7 @@ public final class ActivityThread { private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { - synchronized (mPackages) { + synchronized (mResourcesManager) { WeakReference ref; if (includeCode) { ref = mPackages.get(aInfo.packageName); @@ -1891,6 +1862,7 @@ public final class ActivityThread { } ActivityThread() { + mResourcesManager = ResourcesManager.getInstance(); } public ApplicationThread getApplicationThread() @@ -1903,10 +1875,6 @@ public final class ActivityThread { return mInstrumentation; } - public Configuration getConfiguration() { - return mResConfiguration; - } - public boolean isProfiling() { return mProfiler != null && mProfiler.profileFile != null && mProfiler.profileFd == null; @@ -1936,10 +1904,8 @@ public final class ActivityThread { LoadedApk info = new LoadedApk(this, "android", context, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO); context.init(info, null, this); - context.getResources().updateConfiguration( - getConfiguration(), getDisplayMetricsLocked( - Display.DEFAULT_DISPLAY, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)); + context.getResources().updateConfiguration(mResourcesManager.getConfiguration(), + mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY)); mSystemContext = context; //Slog.i(TAG, "Created system resources " + context.getResources() // + ": " + context.getResources().getConfiguration()); @@ -1965,7 +1931,7 @@ public final class ActivityThread { dalvik.system.VMRuntime.getRuntime().startJitCompilation(); } } - + void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; @@ -2232,7 +2198,7 @@ public final class ActivityThread { DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); for (int displayId : dm.getDisplayIds()) { if (displayId != Display.DEFAULT_DISPLAY) { - Display display = dm.getRealDisplay(displayId); + Display display = dm.getRealDisplay(displayId, r.token); baseContext = appContext.createDisplayContext(display); break; } @@ -2351,7 +2317,7 @@ public final class ActivityThread { performNewIntents(data.token, data.intents); } - public void handleRequestActivityExtras(RequestActivityExtras cmd) { + public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) { Bundle data = new Bundle(); ActivityClientRecord r = mActivities.get(cmd.activityToken); if (r != null) { @@ -2363,11 +2329,22 @@ public final class ActivityThread { } IActivityManager mgr = ActivityManagerNative.getDefault(); try { - mgr.reportTopActivityExtras(cmd.requestToken, data); + mgr.reportAssistContextExtras(cmd.requestToken, data); } catch (RemoteException e) { } } - + + public void handleTranslucentConversionComplete(IBinder token, boolean drawComplete) { + ActivityClientRecord r = mActivities.get(token); + if (r != null) { + r.activity.onTranslucentConversionComplete(drawComplete); + } + } + + public void handleInstallProvider(ProviderInfo info) { + installContentProviders(mInitialApplication, Lists.newArrayList(info)); + } + private static final ThreadLocal sCurrentBroadcastIntent = new ThreadLocal(); /** @@ -2651,7 +2628,8 @@ public final class ActivityThread { try { Service s = mServices.get(info.token); if (s != null) { - PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); + PrintWriter pw = new FastPrintWriter(new FileOutputStream( + info.fd.getFileDescriptor())); s.dump(info.fd.getFileDescriptor(), pw, info.args); pw.flush(); } @@ -2666,7 +2644,8 @@ public final class ActivityThread { try { ActivityClientRecord r = mActivities.get(info.token); if (r != null && r.activity != null) { - PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); + PrintWriter pw = new FastPrintWriter(new FileOutputStream( + info.fd.getFileDescriptor())); r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args); pw.flush(); } @@ -2681,7 +2660,8 @@ public final class ActivityThread { try { ProviderClientRecord r = mLocalProviders.get(info.token); if (r != null && r.mLocalProvider != null) { - PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); + PrintWriter pw = new FastPrintWriter(new FileOutputStream( + info.fd.getFileDescriptor())); r.mLocalProvider.dump(info.fd.getFileDescriptor(), pw, info.args); pw.flush(); } @@ -3331,7 +3311,7 @@ public final class ActivityThread { } private void handleSetCoreSettings(Bundle coreSettings) { - synchronized (mPackages) { + synchronized (mResourcesManager) { mCoreSettings = coreSettings; } } @@ -3339,11 +3319,11 @@ public final class ActivityThread { private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { LoadedApk apk = peekPackageInfo(data.pkg, false); if (apk != null) { - apk.mCompatibilityInfo.set(data.info); + apk.setCompatibilityInfo(data.info); } apk = peekPackageInfo(data.pkg, true); if (apk != null) { - apk.mCompatibilityInfo.set(data.info); + apk.setCompatibilityInfo(data.info); } handleConfigurationChanged(mConfiguration, data.info); WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration); @@ -3421,7 +3401,7 @@ public final class ActivityThread { private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = mActivities.get(token); - Class activityClass = null; + Class activityClass = null; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); if (r != null) { activityClass = r.activity.getClass(); @@ -3576,7 +3556,7 @@ public final class ActivityThread { boolean fromServer) { ActivityClientRecord target = null; - synchronized (mPackages) { + synchronized (mResourcesManager) { for (int i=0; i callbacks = new ArrayList(); - synchronized (mPackages) { - final int N = mAllApplications.size(); - for (int i=0; i 0) { - for (ActivityClientRecord ar : mActivities.values()) { - Activity a = ar.activity; - if (a != null) { - Configuration thisConfig = applyConfigCompatMainThread(mCurDefaultDisplayDpi, - newConfig, ar.packageInfo.mCompatibilityInfo.getIfNeeded()); - if (!ar.activity.mFinished && (allActivities || !ar.paused)) { - // If the activity is currently resumed, its configuration - // needs to change right now. - callbacks.add(a); - } else if (thisConfig != null) { - // Otherwise, we will tell it about the change - // the next time it is resumed or shown. Note that - // the activity manager may, before then, decide the - // activity needs to be destroyed to handle its new - // configuration. - if (DEBUG_CONFIGURATION) { - Slog.v(TAG, "Setting activity " - + ar.activityInfo.name + " newConfig=" + thisConfig); - } - ar.newConfig = thisConfig; + final int NACT = mActivities.size(); + for (int i=0; i>> it = - mActiveResources.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry> entry = it.next(); - Resources r = entry.getValue().get(); - if (r != null) { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " - + r + " config to: " + config); - int displayId = entry.getKey().mDisplayId; - boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); - DisplayMetrics dm = defaultDisplayMetrics; - Configuration overrideConfig = entry.getKey().mOverrideConfiguration; - if (!isDefaultDisplay || overrideConfig != null) { - if (tmpConfig == null) { - tmpConfig = new Configuration(); - } - tmpConfig.setTo(config); - if (!isDefaultDisplay) { - dm = getDisplayMetricsLocked(displayId, null); - applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig); - } - if (overrideConfig != null) { - tmpConfig.updateFrom(overrideConfig); - } - r.updateConfiguration(tmpConfig, dm, compat); - } else { - r.updateConfiguration(config, dm, compat); - } - //Slog.i(TAG, "Updated app resources " + v.getKey() - // + " " + r + ": " + r.getConfiguration()); - } else { - //Slog.i(TAG, "Removing old resources " + v.getKey()); - it.remove(); - } + synchronized (mResourcesManager) { + mResourcesManager.applyConfigurationToResourcesLocked(config, null); } - - return changes != 0; - } - - final void applyNonDefaultDisplayMetricsToConfigurationLocked( - DisplayMetrics dm, Configuration config) { - config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; - config.densityDpi = dm.densityDpi; - config.screenWidthDp = (int)(dm.widthPixels / dm.density); - config.screenHeightDp = (int)(dm.heightPixels / dm.density); - int sl = Configuration.resetScreenLayout(config.screenLayout); - if (dm.widthPixels > dm.heightPixels) { - config.orientation = Configuration.ORIENTATION_LANDSCAPE; - config.screenLayout = Configuration.reduceScreenLayout(sl, - config.screenWidthDp, config.screenHeightDp); - } else { - config.orientation = Configuration.ORIENTATION_PORTRAIT; - config.screenLayout = Configuration.reduceScreenLayout(sl, - config.screenHeightDp, config.screenWidthDp); - } - config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate - config.compatScreenWidthDp = config.screenWidthDp; - config.compatScreenHeightDp = config.screenHeightDp; - config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; } final Configuration applyCompatConfiguration(int displayDensity) { @@ -3966,8 +3849,7 @@ public final class ActivityThread { mCompatConfiguration = new Configuration(); } mCompatConfiguration.setTo(mConfiguration); - if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { - mResCompatibilityInfo.applyToConfiguration(displayDensity, mCompatConfiguration); + if (mResourcesManager.applyCompatConfiguration(displayDensity, mCompatConfiguration)) { config = mCompatConfiguration; } return config; @@ -3977,7 +3859,7 @@ public final class ActivityThread { int configDiff = 0; - synchronized (mPackages) { + synchronized (mResourcesManager) { if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { config = mPendingConfiguration; @@ -3993,9 +3875,9 @@ public final class ActivityThread { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); - - applyConfigurationToResourcesLocked(config, compat); - + + mResourcesManager.applyConfigurationToResourcesLocked(config, compat); + if (mConfiguration == null) { mConfiguration = new Configuration(); } @@ -4022,7 +3904,7 @@ public final class ActivityThread { } } - final void freeTextLayoutCachesIfNeeded(int configDiff) { + static void freeTextLayoutCachesIfNeeded(int configDiff) { if (configDiff != 0) { // Ask text layout engine to free its caches if there is a locale change boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); @@ -4245,7 +4127,7 @@ public final class ActivityThread { * reflect configuration changes. The configuration object passed * in AppBindData can be safely assumed to be up to date */ - applyConfigurationToResourcesLocked(data.config, data.compatInfo); + mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo); mCurDefaultDisplayDpi = data.config.densityDpi; applyCompatConfiguration(mCurDefaultDisplayDpi); @@ -4736,12 +4618,11 @@ public final class ActivityThread { mProviderRefCountMap.remove(jBinder); } - Iterator iter = mProviderMap.values().iterator(); - while (iter.hasNext()) { - ProviderClientRecord pr = iter.next(); + for (int i=mProviderMap.size()-1; i>=0; i--) { + ProviderClientRecord pr = mProviderMap.valueAt(i); IBinder myBinder = pr.mProvider.asBinder(); if (myBinder == jBinder) { - iter.remove(); + mProviderMap.removeAt(i); } } } @@ -4770,15 +4651,14 @@ public final class ActivityThread { if (DEBUG_PROVIDER) Slog.v(TAG, "Cleaning up dead provider " + provider + " " + prc.holder.info.name); mProviderRefCountMap.remove(provider); - if (prc.client != null && prc.client.mNames != null) { - for (String name : prc.client.mNames) { - ProviderClientRecord pr = mProviderMap.get(name); - if (pr != null && pr.mProvider.asBinder() == provider) { - Slog.i(TAG, "Removing dead content provider: " + name); - mProviderMap.remove(name); - } + for (int i=mProviderMap.size()-1; i>=0; i--) { + ProviderClientRecord pr = mProviderMap.valueAt(i); + if (pr != null && pr.mProvider.asBinder() == provider) { + Slog.i(TAG, "Removing dead content provider:" + pr.mProvider.toString()); + mProviderMap.removeAt(i); } } + if (fromClient) { // We found out about this due to execution in our client // code. Tell the activity manager about it now, to ensure @@ -4795,6 +4675,19 @@ public final class ActivityThread { } } + final void appNotRespondingViaProvider(IBinder provider) { + synchronized (mProviderMap) { + ProviderRefCount prc = mProviderRefCountMap.get(provider); + if (prc != null) { + try { + ActivityManagerNative.getDefault() + .appNotRespondingViaProvider(prc.holder.connection); + } catch (RemoteException e) { + } + } + } + } + private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider, ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) { final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority); @@ -4957,6 +4850,7 @@ public final class ActivityThread { mSystemThread = system; if (!system) { ViewRootImpl.addFirstDrawHandler(new Runnable() { + @Override public void run() { ensureJitEnabled(); } @@ -4993,12 +4887,13 @@ public final class ActivityThread { DropBox.setReporter(new DropBoxReporter()); ViewRootImpl.addConfigCallback(new ComponentCallbacks2() { + @Override public void onConfigurationChanged(Configuration newConfig) { - synchronized (mPackages) { + synchronized (mResourcesManager) { // We need to apply this change to the resources // immediately, because upon returning the view // hierarchy will be informed about it. - if (applyConfigurationToResourcesLocked(newConfig, null)) { + if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) { // This actually changed the resources! Tell // everyone about it. if (mPendingConfiguration == null || @@ -5010,8 +4905,10 @@ public final class ActivityThread { } } } + @Override public void onLowMemory() { } + @Override public void onTrimMemory(int level) { } }); @@ -5031,12 +4928,11 @@ public final class ActivityThread { } public int getIntCoreSetting(String key, int defaultValue) { - synchronized (mPackages) { + synchronized (mResourcesManager) { if (mCoreSettings != null) { return mCoreSettings.getInt(key, defaultValue); - } else { - return defaultValue; } + return defaultValue; } } diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 2fe682d1861f7521175d7bb8f850c6404a0e8b35..5c3a3e5cde6c25e94cbc4554aa962a5a6fbf159a 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -16,8 +16,11 @@ package android.app; +import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.RemoteException; +import android.os.WorkSource; /** * This class provides access to the system alarm services. These allow you @@ -52,6 +55,8 @@ import android.os.RemoteException; */ public class AlarmManager { + private static final String TAG = "AlarmManager"; + /** * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()} * (wall clock time in UTC), which will wake up the device when @@ -80,17 +85,33 @@ public class AlarmManager */ public static final int ELAPSED_REALTIME = 3; + /** @hide */ + public static final long WINDOW_EXACT = 0; + /** @hide */ + public static final long WINDOW_HEURISTIC = -1; + private final IAlarmManager mService; + private final boolean mAlwaysExact; + /** * package private on purpose */ - AlarmManager(IAlarmManager service) { + AlarmManager(IAlarmManager service, Context ctx) { mService = service; + + final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion; + mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT); } - + + private long legacyExactLength() { + return (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC); + } + /** - * Schedule an alarm. Note: for timing operations (ticks, timeouts, + * TBW: discussion of fuzzy nature of alarms in KLP+. + * + *

Schedule an alarm. Note: for timing operations (ticks, timeouts, * etc) it is easier and much more efficient to use * {@link android.os.Handler}. If there is already an alarm scheduled * for the same IntentSender, it will first be canceled. @@ -122,7 +143,9 @@ public class AlarmManager * IntentSender.getBroadcast()}. * * @see android.os.Handler + * @see #setExact * @see #setRepeating + * @see #setWindow * @see #cancel * @see android.content.Context#sendBroadcast * @see android.content.Context#registerReceiver @@ -133,10 +156,7 @@ public class AlarmManager * @see #RTC_WAKEUP */ public void set(int type, long triggerAtMillis, PendingIntent operation) { - try { - mService.set(type, triggerAtMillis, operation); - } catch (RemoteException ex) { - } + setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null); } /** @@ -177,6 +197,8 @@ public class AlarmManager * * @see android.os.Handler * @see #set + * @see #setExact + * @see #setWindow * @see #cancel * @see android.content.Context#sendBroadcast * @see android.content.Context#registerReceiver @@ -188,22 +210,113 @@ public class AlarmManager */ public void setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null); + } + + /** + * Schedule an alarm to be delivered within a given window of time. + * + * TBW: clean up these docs + * + * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or + * RTC_WAKEUP. + * @param windowStartMillis The earliest time, in milliseconds, that the alarm should + * be delivered, expressed in the appropriate clock's units (depending on the alarm + * type). + * @param windowLengthMillis The length of the requested delivery window, + * in milliseconds. The alarm will be delivered no later than this many + * milliseconds after the windowStartMillis time. Note that this parameter + * is a duration, not the timestamp of the end of the window. + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see #set + * @see #setExact + * @see #setRepeating + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + */ + public void setWindow(int type, long windowStartMillis, long windowLengthMillis, + PendingIntent operation) { + setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null); + } + + /** + * TBW: new 'exact' alarm that must be delivered as nearly as possible + * to the precise time specified. + */ + public void setExact(int type, long triggerAtMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null); + } + + /** @hide */ + public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis, + PendingIntent operation, WorkSource workSource) { + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource); + } + + private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis, + PendingIntent operation, WorkSource workSource) { + if (triggerAtMillis < 0) { + /* NOTYET + if (mAlwaysExact) { + // Fatal error for KLP+ apps to use negative trigger times + throw new IllegalArgumentException("Invalid alarm trigger time " + + triggerAtMillis); + } + */ + triggerAtMillis = 0; + } + try { - mService.setRepeating(type, triggerAtMillis, intervalMillis, operation); + mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation, + workSource); } catch (RemoteException ex) { } } /** - * Available inexact recurrence intervals recognized by - * {@link #setInexactRepeating(int, long, long, PendingIntent)} + * @deprecated setInexactRepeating() is deprecated; as of API 19 all + * repeating alarms are inexact. */ + @Deprecated public static final long INTERVAL_FIFTEEN_MINUTES = 15 * 60 * 1000; + + /** + * @deprecated setInexactRepeating() is deprecated; as of API 19 all + * repeating alarms are inexact. + */ + @Deprecated public static final long INTERVAL_HALF_HOUR = 2*INTERVAL_FIFTEEN_MINUTES; + + /** + * @deprecated setInexactRepeating() is deprecated; as of API 19 all + * repeating alarms are inexact. + */ + @Deprecated public static final long INTERVAL_HOUR = 2*INTERVAL_HALF_HOUR; + + /** + * @deprecated setInexactRepeating() is deprecated; as of API 19 all + * repeating alarms are inexact. + */ + @Deprecated public static final long INTERVAL_HALF_DAY = 12*INTERVAL_HOUR; + + /** + * @deprecated setInexactRepeating() is deprecated; as of API 19 all + * repeating alarms are inexact. + */ + @Deprecated public static final long INTERVAL_DAY = 2*INTERVAL_HALF_DAY; - + /** * Schedule a repeating alarm that has inexact trigger time requirements; * for example, an alarm that repeats every hour, but not necessarily at @@ -236,6 +349,8 @@ public class AlarmManager * typically comes from {@link PendingIntent#getBroadcast * IntentSender.getBroadcast()}. * + * @deprecated As of API 19, all repeating alarms are inexact. + * * @see android.os.Handler * @see #set * @see #cancel @@ -252,12 +367,10 @@ public class AlarmManager * @see #INTERVAL_HALF_DAY * @see #INTERVAL_DAY */ + @Deprecated public void setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { - try { - mService.setInexactRepeating(type, triggerAtMillis, intervalMillis, operation); - } catch (RemoteException ex) { - } + setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null); } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1330ce19cf0c5625cfa05bf5ee9fc01ae9bad00c..aece46298364d7cd7713046c56d8d2118f4f60ec 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,7 +16,9 @@ package android.app; -import android.Manifest; +import android.os.Binder; +import android.os.IBinder; +import android.util.ArrayMap; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsCallback; @@ -31,79 +33,179 @@ import android.os.Process; import android.os.RemoteException; /** - * API for interacting with "application operation" tracking. Allows you to: + * API for interacting with "application operation" tracking. * - * - Note when operations are happening, and find out if they are allowed for the current caller. - * - Disallow specific apps from doing specific operations. - * - Collect all of the current information about operations that have been executed or are not - * being allowed. - * - Monitor for changes in whether an operation is allowed. - * - * Each operation is identified by a single integer; these integers are a fixed set of - * operations, enumerated by the OP_* constants. - * - * When checking operations, the result is a "mode" integer indicating the current setting - * for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute the operation but - * fake its behavior enough so that the caller doesn't crash), MODE_ERRORED (through a - * SecurityException back to the caller; the normal operation calls will do this for you). - * - * @hide + *

This API is not generally intended for third party application developers; most + * features are only available to system applicatins. Obtain an instance of it through + * {@link Context#getSystemService(String) Context.getSystemService} with + * {@link Context#APP_OPS_SERVICE Context.APP_OPS_SERVICE}.

*/ public class AppOpsManager { + /** + *

App ops allows callers to:

+ * + *
    + *
  • Note when operations are happening, and find out if they are allowed for the current + * caller.
  • + *
  • Disallow specific apps from doing specific operations.
  • + *
  • Collect all of the current information about operations that have been executed or + * are not being allowed.
  • + *
  • Monitor for changes in whether an operation is allowed.
  • + *
+ * + *

Each operation is identified by a single integer; these integers are a fixed set of + * operations, enumerated by the OP_* constants. + * + *

When checking operations, the result is a "mode" integer indicating the current + * setting for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute + * the operation but fake its behavior enough so that the caller doesn't crash), + * MODE_ERRORED (throw a SecurityException back to the caller; the normal operation calls + * will do this for you). + */ + final Context mContext; final IAppOpsService mService; - final HashMap mModeWatchers - = new HashMap(); + final ArrayMap mModeWatchers + = new ArrayMap(); + static IBinder sToken; + + /** + * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is + * allowed to perform the given operation. + */ public static final int MODE_ALLOWED = 0; + + /** + * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is + * not allowed to perform the given operation, and this attempt should + * silently fail (it should not cause the app to crash). + */ public static final int MODE_IGNORED = 1; + + /** + * Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: the + * given caller is not allowed to perform the given operation, and this attempt should + * cause it to have a fatal error, typically a {@link SecurityException}. + */ public static final int MODE_ERRORED = 2; // when adding one of these: // - increment _NUM_OP - // - add rows to sOpToSwitch, sOpNames, sOpPerms + // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode // - add descriptive strings to Settings/res/values/arrays.xml + // - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app) + + /** @hide No operation specified. */ public static final int OP_NONE = -1; + /** @hide Access to coarse location information. */ public static final int OP_COARSE_LOCATION = 0; + /** @hide Access to fine location information. */ public static final int OP_FINE_LOCATION = 1; + /** @hide Causing GPS to run. */ public static final int OP_GPS = 2; + /** @hide */ public static final int OP_VIBRATE = 3; + /** @hide */ public static final int OP_READ_CONTACTS = 4; + /** @hide */ public static final int OP_WRITE_CONTACTS = 5; + /** @hide */ public static final int OP_READ_CALL_LOG = 6; + /** @hide */ public static final int OP_WRITE_CALL_LOG = 7; + /** @hide */ public static final int OP_READ_CALENDAR = 8; + /** @hide */ public static final int OP_WRITE_CALENDAR = 9; + /** @hide */ public static final int OP_WIFI_SCAN = 10; + /** @hide */ public static final int OP_POST_NOTIFICATION = 11; + /** @hide */ public static final int OP_NEIGHBORING_CELLS = 12; + /** @hide */ public static final int OP_CALL_PHONE = 13; + /** @hide */ public static final int OP_READ_SMS = 14; + /** @hide */ public static final int OP_WRITE_SMS = 15; + /** @hide */ public static final int OP_RECEIVE_SMS = 16; + /** @hide */ public static final int OP_RECEIVE_EMERGECY_SMS = 17; + /** @hide */ public static final int OP_RECEIVE_MMS = 18; + /** @hide */ public static final int OP_RECEIVE_WAP_PUSH = 19; + /** @hide */ public static final int OP_SEND_SMS = 20; + /** @hide */ public static final int OP_READ_ICC_SMS = 21; + /** @hide */ public static final int OP_WRITE_ICC_SMS = 22; + /** @hide */ public static final int OP_WRITE_SETTINGS = 23; + /** @hide */ public static final int OP_SYSTEM_ALERT_WINDOW = 24; + /** @hide */ public static final int OP_ACCESS_NOTIFICATIONS = 25; + /** @hide */ public static final int OP_CAMERA = 26; + /** @hide */ public static final int OP_RECORD_AUDIO = 27; + /** @hide */ public static final int OP_PLAY_AUDIO = 28; + /** @hide */ public static final int OP_READ_CLIPBOARD = 29; + /** @hide */ public static final int OP_WRITE_CLIPBOARD = 30; /** @hide */ - public static final int _NUM_OP = 31; + public static final int OP_TAKE_MEDIA_BUTTONS = 31; + /** @hide */ + public static final int OP_TAKE_AUDIO_FOCUS = 32; + /** @hide */ + public static final int OP_AUDIO_MASTER_VOLUME = 33; + /** @hide */ + public static final int OP_AUDIO_VOICE_VOLUME = 34; + /** @hide */ + public static final int OP_AUDIO_RING_VOLUME = 35; + /** @hide */ + public static final int OP_AUDIO_MEDIA_VOLUME = 36; + /** @hide */ + public static final int OP_AUDIO_ALARM_VOLUME = 37; + /** @hide */ + public static final int OP_AUDIO_NOTIFICATION_VOLUME = 38; + /** @hide */ + public static final int OP_AUDIO_BLUETOOTH_VOLUME = 39; + /** @hide */ + public static final int OP_WAKE_LOCK = 40; + /** @hide Continually monitoring location data. */ + public static final int OP_MONITOR_LOCATION = 41; + /** @hide Continually monitoring location data with a relatively high power request. */ + public static final int OP_MONITOR_HIGH_POWER_LOCATION = 42; + /** @hide */ + public static final int _NUM_OP = 43; + + /** Access to coarse location information. */ + public static final String OPSTR_COARSE_LOCATION = + "android:coarse_location"; + /** Access to fine location information. */ + public static final String OPSTR_FINE_LOCATION = + "android:fine_location"; + /** Continually monitoring location data. */ + public static final String OPSTR_MONITOR_LOCATION + = "android:monitor_location"; + /** Continually monitoring location data with a relatively high power request. */ + public static final String OPSTR_MONITOR_HIGH_POWER_LOCATION + = "android:monitor_location_high_power"; /** * This maps each operation to the operation that serves as the * switch to determine whether it is allowed. Generally this is * a 1:1 mapping, but for some things (like location) that have * multiple low-level operations being tracked that should be - * presented to hte user as one switch then this can be used to + * presented to the user as one switch then this can be used to * make them all controlled by the same single operation. */ private static int[] sOpToSwitch = new int[] { @@ -123,11 +225,11 @@ public class AppOpsManager { OP_CALL_PHONE, OP_READ_SMS, OP_WRITE_SMS, - OP_READ_SMS, - OP_READ_SMS, - OP_READ_SMS, - OP_READ_SMS, - OP_WRITE_SMS, + OP_RECEIVE_SMS, + OP_RECEIVE_SMS, + OP_RECEIVE_SMS, + OP_RECEIVE_SMS, + OP_SEND_SMS, OP_READ_SMS, OP_WRITE_SMS, OP_WRITE_SETTINGS, @@ -138,6 +240,68 @@ public class AppOpsManager { OP_PLAY_AUDIO, OP_READ_CLIPBOARD, OP_WRITE_CLIPBOARD, + OP_TAKE_MEDIA_BUTTONS, + OP_TAKE_AUDIO_FOCUS, + OP_AUDIO_MASTER_VOLUME, + OP_AUDIO_VOICE_VOLUME, + OP_AUDIO_RING_VOLUME, + OP_AUDIO_MEDIA_VOLUME, + OP_AUDIO_ALARM_VOLUME, + OP_AUDIO_NOTIFICATION_VOLUME, + OP_AUDIO_BLUETOOTH_VOLUME, + OP_WAKE_LOCK, + OP_COARSE_LOCATION, + OP_COARSE_LOCATION, + }; + + /** + * This maps each operation to the public string constant for it. + * If it doesn't have a public string constant, it maps to null. + */ + private static String[] sOpToString = new String[] { + OPSTR_COARSE_LOCATION, + OPSTR_FINE_LOCATION, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + OPSTR_MONITOR_LOCATION, + OPSTR_MONITOR_HIGH_POWER_LOCATION, }; /** @@ -176,6 +340,18 @@ public class AppOpsManager { "PLAY_AUDIO", "READ_CLIPBOARD", "WRITE_CLIPBOARD", + "TAKE_MEDIA_BUTTONS", + "TAKE_AUDIO_FOCUS", + "AUDIO_MASTER_VOLUME", + "AUDIO_VOICE_VOLUME", + "AUDIO_RING_VOLUME", + "AUDIO_MEDIA_VOLUME", + "AUDIO_ALARM_VOLUME", + "AUDIO_NOTIFICATION_VOLUME", + "AUDIO_BLUETOOTH_VOLUME", + "WAKE_LOCK", + "MONITOR_LOCATION", + "MONITOR_HIGH_POWER_LOCATION", }; /** @@ -214,10 +390,159 @@ public class AppOpsManager { null, // no permission for playing audio null, // no permission for reading clipboard null, // no permission for writing clipboard + null, // no permission for taking media buttons + null, // no permission for taking audio focus + null, // no permission for changing master volume + null, // no permission for changing voice volume + null, // no permission for changing ring volume + null, // no permission for changing media volume + null, // no permission for changing alarm volume + null, // no permission for changing notification volume + null, // no permission for changing bluetooth volume + android.Manifest.permission.WAKE_LOCK, + null, // no permission for generic location monitoring + null, // no permission for high power location monitoring }; + /** + * This specifies the default mode for each operation. + */ + private static int[] sOpDefaultMode = new int[] { + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_IGNORED, // OP_WRITE_SMS + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + }; + + /** + * This specifies whether each option is allowed to be reset + * when resetting all app preferences. Disable reset for + * app ops that are under strong control of some part of the + * system (such as OP_WRITE_SMS, which should be allowed only + * for whichever app is selected as the current SMS app). + */ + private static boolean[] sOpDisableReset = new boolean[] { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, // OP_WRITE_SMS + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + }; + + private static HashMap sOpStrToOp = new HashMap(); + + static { + if (sOpToSwitch.length != _NUM_OP) { + throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length + + " should be " + _NUM_OP); + } + if (sOpToString.length != _NUM_OP) { + throw new IllegalStateException("sOpToString length " + sOpToString.length + + " should be " + _NUM_OP); + } + if (sOpNames.length != _NUM_OP) { + throw new IllegalStateException("sOpNames length " + sOpNames.length + + " should be " + _NUM_OP); + } + if (sOpPerms.length != _NUM_OP) { + throw new IllegalStateException("sOpPerms length " + sOpPerms.length + + " should be " + _NUM_OP); + } + if (sOpDefaultMode.length != _NUM_OP) { + throw new IllegalStateException("sOpDefaultMode length " + sOpDefaultMode.length + + " should be " + _NUM_OP); + } + if (sOpDisableReset.length != _NUM_OP) { + throw new IllegalStateException("sOpDisableReset length " + sOpDisableReset.length + + " should be " + _NUM_OP); + } + for (int i=0; i<_NUM_OP; i++) { + if (sOpToString[i] != null) { + sOpStrToOp.put(sOpToString[i], i); + } + } + } + /** * Retrieve the op switch that controls the given operation. + * @hide */ public static int opToSwitch(int op) { return sOpToSwitch[op]; @@ -225,6 +550,7 @@ public class AppOpsManager { /** * Retrieve a non-localized name for the operation, for debugging output. + * @hide */ public static String opToName(int op) { if (op == OP_NONE) return "NONE"; @@ -233,13 +559,31 @@ public class AppOpsManager { /** * Retrieve the permission associated with an operation, or null if there is not one. + * @hide */ public static String opToPermission(int op) { return sOpPerms[op]; } + /** + * Retrieve the default mode for the operation. + * @hide + */ + public static int opToDefaultMode(int op) { + return sOpDefaultMode[op]; + } + + /** + * Retrieve whether the op allows itself to be reset. + * @hide + */ + public static boolean opAllowsReset(int op) { + return !sOpDisableReset[op]; + } + /** * Class holding all of the operation information associated with an app. + * @hide */ public static class PackageOps implements Parcelable { private final String mPackageName; @@ -302,6 +646,7 @@ public class AppOpsManager { /** * Class holding the information about one unique operation of an application. + * @hide */ public static class OpEntry implements Parcelable { private final int mOp; @@ -378,11 +723,21 @@ public class AppOpsManager { /** * Callback for notification of changes to operation state. */ - public interface Callback { - public void opChanged(int op, String packageName); + public interface OnOpChangedListener { + public void onOpChanged(String op, String packageName); } - public AppOpsManager(Context context, IAppOpsService service) { + /** + * Callback for notification of changes to operation state. + * This allows you to see the raw op codes instead of strings. + * @hide + */ + public static class OnOpChangedInternalListener implements OnOpChangedListener { + public void onOpChanged(String op, String packageName) { } + public void onOpChanged(int op, String packageName) { } + } + + AppOpsManager(Context context, IAppOpsService service) { mContext = context; mService = service; } @@ -391,6 +746,7 @@ public class AppOpsManager { * Retrieve current operation state for all applications. * * @param ops The set of operations you are interested in, or null if you want all of them. + * @hide */ public List getPackagesForOps(int[] ops) { try { @@ -406,6 +762,7 @@ public class AppOpsManager { * @param uid The uid of the application of interest. * @param packageName The name of the application of interest. * @param ops The set of operations you are interested in, or null if you want all of them. + * @hide */ public List getOpsForPackage(int uid, String packageName, int[] ops) { try { @@ -415,6 +772,7 @@ public class AppOpsManager { return null; } + /** @hide */ public void setMode(int code, int uid, String packageName, int mode) { try { mService.setMode(code, uid, packageName, mode); @@ -430,13 +788,36 @@ public class AppOpsManager { } } - public void startWatchingMode(int op, String packageName, final Callback callback) { + /** + * Monitor for changes to the operating mode for the given op in the given app package. + * @param op The operation to monitor, one of OPSTR_*. + * @param packageName The name of the application to monitor. + * @param callback Where to report changes. + */ + public void startWatchingMode(String op, String packageName, + final OnOpChangedListener callback) { + startWatchingMode(strOpToOp(op), packageName, callback); + } + + /** + * Monitor for changes to the operating mode for the given op in the given app package. + * @param op The operation to monitor, one of OP_*. + * @param packageName The name of the application to monitor. + * @param callback Where to report changes. + * @hide + */ + public void startWatchingMode(int op, String packageName, final OnOpChangedListener callback) { synchronized (mModeWatchers) { IAppOpsCallback cb = mModeWatchers.get(callback); if (cb == null) { cb = new IAppOpsCallback.Stub() { public void opChanged(int op, String packageName) { - callback.opChanged(op, packageName); + if (callback instanceof OnOpChangedInternalListener) { + ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName); + } + if (sOpToString[op] != null) { + callback.onOpChanged(sOpToString[op], packageName); + } } }; mModeWatchers.put(callback, cb); @@ -448,7 +829,11 @@ public class AppOpsManager { } } - public void stopWatchingMode(Callback callback) { + /** + * Stop monitoring that was previously started with {@link #startWatchingMode}. All + * monitoring associated with this callback will be removed. + */ + public void stopWatchingMode(OnOpChangedListener callback) { synchronized (mModeWatchers) { IAppOpsCallback cb = mModeWatchers.get(callback); if (cb != null) { @@ -460,11 +845,132 @@ public class AppOpsManager { } } + private String buildSecurityExceptionMsg(int op, int uid, String packageName) { + return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op]; + } + + private int strOpToOp(String op) { + Integer val = sOpStrToOp.get(op); + if (val == null) { + throw new IllegalArgumentException("Unknown operation string: " + op); + } + return val; + } + + /** + * Do a quick check for whether an application might be able to perform an operation. + * This is not a security check; you must use {@link #noteOp(String, int, String)} + * or {@link #startOp(String, int, String)} for your actual security checks, which also + * ensure that the given uid and package name are consistent. This function can just be + * used for a quick check to see if an operation has been disabled for the application, + * as an early reject of some work. This does not modify the time stamp or other data + * about the operation. + * @param op The operation to check. One of the OPSTR_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + */ + public int checkOp(String op, int uid, String packageName) { + return checkOp(strOpToOp(op), uid, packageName); + } + + /** + * Like {@link #checkOp but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + */ + public int checkOpNoThrow(String op, int uid, String packageName) { + return checkOpNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * Make note of an application performing an operation. Note that you must pass + * in both the uid and name of the application to be checked; this function will verify + * that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time. + * @param op The operation to note. One of the OPSTR_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + */ + public int noteOp(String op, int uid, String packageName) { + return noteOp(strOpToOp(op), uid, packageName); + } + + /** + * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + */ + public int noteOpNoThrow(String op, int uid, String packageName) { + return noteOpNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * Report that an application has started executing a long-running operation. Note that you + * must pass in both the uid and name of the application to be checked; this function will + * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time and the operation will be marked as "running". In this case you must + * later call {@link #finishOp(String, int, String)} to report when the application is no + * longer performing the operation. + * @param op The operation to start. One of the OPSTR_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + */ + public int startOp(String op, int uid, String packageName) { + return startOp(strOpToOp(op), uid, packageName); + } + + /** + * Like {@link #startOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + */ + public int startOpNoThrow(String op, int uid, String packageName) { + return startOpNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * Report that an application is no longer performing an operation that had previously + * been started with {@link #startOp(String, int, String)}. There is no validation of input + * or result; the parameters supplied here must be the exact same ones previously passed + * in when starting the operation. + */ + public void finishOp(String op, int uid, String packageName) { + finishOp(strOpToOp(op), uid, packageName); + } + + /** + * Do a quick check for whether an application might be able to perform an operation. + * This is not a security check; you must use {@link #noteOp(int, int, String)} + * or {@link #startOp(int, int, String)} for your actual security checks, which also + * ensure that the given uid and package name are consistent. This function can just be + * used for a quick check to see if an operation has been disabled for the application, + * as an early reject of some work. This does not modify the time stamp or other data + * about the operation. + * @param op The operation to check. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + * @hide + */ public int checkOp(int op, int uid, String packageName) { try { int mode = mService.checkOperation(op, uid, packageName); if (mode == MODE_ERRORED) { - throw new SecurityException("Operation not allowed"); + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } return mode; } catch (RemoteException e) { @@ -472,6 +978,11 @@ public class AppOpsManager { return MODE_IGNORED; } + /** + * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + * @hide + */ public int checkOpNoThrow(int op, int uid, String packageName) { try { return mService.checkOperation(op, uid, packageName); @@ -480,11 +991,43 @@ public class AppOpsManager { return MODE_IGNORED; } + /** + * Do a quick check to validate if a package name belongs to a UID. + * + * @throws SecurityException if the package name doesn't belong to the given + * UID, or if ownership cannot be verified. + */ + public void checkPackage(int uid, String packageName) { + try { + if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) { + throw new SecurityException( + "Package " + packageName + " does not belong to " + uid); + } + } catch (RemoteException e) { + throw new SecurityException("Unable to verify package ownership", e); + } + } + + /** + * Make note of an application performing an operation. Note that you must pass + * in both the uid and name of the application to be checked; this function will verify + * that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time. + * @param op The operation to note. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + * @hide + */ public int noteOp(int op, int uid, String packageName) { try { int mode = mService.noteOperation(op, uid, packageName); if (mode == MODE_ERRORED) { - throw new SecurityException("Operation not allowed"); + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } return mode; } catch (RemoteException e) { @@ -492,6 +1035,11 @@ public class AppOpsManager { return MODE_IGNORED; } + /** + * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + * @hide + */ public int noteOpNoThrow(int op, int uid, String packageName) { try { return mService.noteOperation(op, uid, packageName); @@ -500,15 +1048,48 @@ public class AppOpsManager { return MODE_IGNORED; } + /** @hide */ public int noteOp(int op) { - return noteOp(op, Process.myUid(), mContext.getBasePackageName()); + return noteOp(op, Process.myUid(), mContext.getOpPackageName()); } + /** @hide */ + public static IBinder getToken(IAppOpsService service) { + synchronized (AppOpsManager.class) { + if (sToken != null) { + return sToken; + } + try { + sToken = service.getToken(new Binder()); + } catch (RemoteException e) { + // System is dead, whatevs. + } + return sToken; + } + } + + /** + * Report that an application has started executing a long-running operation. Note that you + * must pass in both the uid and name of the application to be checked; this function will + * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time and the operation will be marked as "running". In this case you must + * later call {@link #finishOp(int, int, String)} to report when the application is no + * longer performing the operation. + * @param op The operation to start. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + * @hide + */ public int startOp(int op, int uid, String packageName) { try { - int mode = mService.startOperation(op, uid, packageName); + int mode = mService.startOperation(getToken(mService), op, uid, packageName); if (mode == MODE_ERRORED) { - throw new SecurityException("Operation not allowed"); + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } return mode; } catch (RemoteException e) { @@ -516,26 +1097,40 @@ public class AppOpsManager { return MODE_IGNORED; } + /** + * Like {@link #startOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + * @hide + */ public int startOpNoThrow(int op, int uid, String packageName) { try { - return mService.startOperation(op, uid, packageName); + return mService.startOperation(getToken(mService), op, uid, packageName); } catch (RemoteException e) { } return MODE_IGNORED; } + /** @hide */ public int startOp(int op) { - return startOp(op, Process.myUid(), mContext.getBasePackageName()); + return startOp(op, Process.myUid(), mContext.getOpPackageName()); } + /** + * Report that an application is no longer performing an operation that had previously + * been started with {@link #startOp(int, int, String)}. There is no validation of input + * or result; the parameters supplied here must be the exact same ones previously passed + * in when starting the operation. + * @hide + */ public void finishOp(int op, int uid, String packageName) { try { - mService.finishOperation(op, uid, packageName); + mService.finishOperation(getToken(mService), op, uid, packageName); } catch (RemoteException e) { } } + /** @hide */ public void finishOp(int op) { - finishOp(op, Process.myUid(), mContext.getBasePackageName()); + finishOp(op, Process.myUid(), mContext.getOpPackageName()); } } diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 954476dbeb488c0012a1e7dc792750c3add17cb6..c1174861049411c78f7299405f188158cb09f599 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -28,6 +28,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; import android.util.Printer; +import com.android.internal.util.FastPrintWriter; import java.io.PrintWriter; import java.io.StringWriter; @@ -327,7 +328,9 @@ public class ApplicationErrorReport implements Parcelable { */ public CrashInfo(Throwable tr) { StringWriter sw = new StringWriter(); - tr.printStackTrace(new PrintWriter(sw)); + PrintWriter pw = new FastPrintWriter(sw, false, 256); + tr.printStackTrace(pw); + pw.flush(); stackTrace = sw.toString(); exceptionMessage = tr.getMessage(); diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java index a26b88cd4af06384db9d7d0ddaf5f30823fedf4c..413c3699db08897ae93112f37cc54a245bc93661 100644 --- a/core/java/android/app/ApplicationLoaders.java +++ b/core/java/android/app/ApplicationLoaders.java @@ -17,11 +17,9 @@ package android.app; import android.os.Trace; +import android.util.ArrayMap; import dalvik.system.PathClassLoader; -import java.util.HashMap; -import java.util.Map; - class ApplicationLoaders { public static ApplicationLoaders getDefault() @@ -71,7 +69,7 @@ class ApplicationLoaders } } - private final Map mLoaders = new HashMap(); + private final ArrayMap mLoaders = new ArrayMap(); private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders(); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 271494f1d215c2d904771e19a713ccc40e040e42..b505d4f8e878823f121417b57100ecf1c73abc29 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -51,6 +51,7 @@ import android.net.Uri; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; import android.view.Display; @@ -583,6 +584,22 @@ final class ApplicationPackageManager extends PackageManager { return queryIntentServicesAsUser(intent, flags, mContext.getUserId()); } + @Override + public List queryIntentContentProvidersAsUser( + Intent intent, int flags, int userId) { + try { + return mPM.queryIntentContentProviders(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, userId); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List queryIntentContentProviders(Intent intent, int flags) { + return queryIntentContentProvidersAsUser(intent, flags, mContext.getUserId()); + } + @Override public ProviderInfo resolveContentProvider(String name, int flags) { @@ -859,26 +876,20 @@ final class ApplicationPackageManager extends PackageManager { boolean needCleanup = false; for (String ssp : pkgList) { synchronized (sSync) { - if (sIconCache.size() > 0) { - Iterator it = sIconCache.keySet().iterator(); - while (it.hasNext()) { - ResourceName nm = it.next(); - if (nm.packageName.equals(ssp)) { - //Log.i(TAG, "Removing cached drawable for " + nm); - it.remove(); - needCleanup = true; - } + for (int i=sIconCache.size()-1; i>=0; i--) { + ResourceName nm = sIconCache.keyAt(i); + if (nm.packageName.equals(ssp)) { + //Log.i(TAG, "Removing cached drawable for " + nm); + sIconCache.removeAt(i); + needCleanup = true; } } - if (sStringCache.size() > 0) { - Iterator it = sStringCache.keySet().iterator(); - while (it.hasNext()) { - ResourceName nm = it.next(); - if (nm.packageName.equals(ssp)) { - //Log.i(TAG, "Removing cached string for " + nm); - it.remove(); - needCleanup = true; - } + for (int i=sStringCache.size()-1; i>=0; i--) { + ResourceName nm = sStringCache.keyAt(i); + if (nm.packageName.equals(ssp)) { + //Log.i(TAG, "Removing cached string for " + nm); + sStringCache.removeAt(i); + needCleanup = true; } } } @@ -1255,6 +1266,16 @@ final class ApplicationPackageManager extends PackageManager { return 0; } + @Override + public ComponentName getHomeActivities(List outActivities) { + try { + return mPM.getHomeActivities(outActivities); + } catch (RemoteException e) { + // Should never happen! + } + return null; + } + @Override public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) { @@ -1280,7 +1301,7 @@ final class ApplicationPackageManager extends PackageManager { int newState, int flags) { try { mPM.setApplicationEnabledSetting(packageName, newState, flags, - mContext.getUserId(), mContext.getBasePackageName()); + mContext.getUserId(), mContext.getOpPackageName()); } catch (RemoteException e) { // Should never happen! } @@ -1296,6 +1317,28 @@ final class ApplicationPackageManager extends PackageManager { return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; } + @Override + public boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked, + UserHandle user) { + try { + return mPM.setApplicationBlockedSettingAsUser(packageName, blocked, + user.getIdentifier()); + } catch (RemoteException re) { + // Should never happen! + } + return false; + } + + @Override + public boolean getApplicationBlockedSettingAsUser(String packageName, UserHandle user) { + try { + return mPM.getApplicationBlockedSettingAsUser(packageName, user.getIdentifier()); + } catch (RemoteException re) { + // Should never happen! + } + return false; + } + /** * @hide */ @@ -1313,8 +1356,8 @@ final class ApplicationPackageManager extends PackageManager { private final IPackageManager mPM; private static final Object sSync = new Object(); - private static HashMap> sIconCache - = new HashMap>(); - private static HashMap> sStringCache - = new HashMap>(); + private static ArrayMap> sIconCache + = new ArrayMap>(); + private static ArrayMap> sStringCache + = new ArrayMap>(); } diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index b1c58f234c392817e4c6cc90f6fa0c922cc16b2c..347d43f63a69110d6fd76c15f6ccc63a5ffd6f5b 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -111,8 +111,9 @@ public abstract class ApplicationThreadNative extends Binder { data.enforceInterface(IApplicationThread.descriptor); IBinder b = data.readStrongBinder(); + int procState = data.readInt(); boolean isForward = data.readInt() != 0; - scheduleResumeActivity(b, isForward); + scheduleResumeActivity(b, procState, isForward); return true; } @@ -134,6 +135,7 @@ public abstract class ApplicationThreadNative extends Binder ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); Configuration curConfig = Configuration.CREATOR.createFromParcel(data); CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data); + int procState = data.readInt(); Bundle state = data.readBundle(); List ri = data.createTypedArrayList(ResultInfo.CREATOR); List pi = data.createTypedArrayList(Intent.CREATOR); @@ -141,10 +143,10 @@ public abstract class ApplicationThreadNative extends Binder boolean isForward = data.readInt() != 0; String profileName = data.readString(); ParcelFileDescriptor profileFd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; boolean autoStopProfiler = data.readInt() != 0; - scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, state, ri, pi, - notResumed, isForward, profileName, profileFd, autoStopProfiler); + scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state, + ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler); return true; } @@ -194,8 +196,9 @@ public abstract class ApplicationThreadNative extends Binder Bundle resultExtras = data.readBundle(); boolean sync = data.readInt() != 0; int sendingUser = data.readInt(); + int processState = data.readInt(); scheduleReceiver(intent, info, compatInfo, resultCode, resultData, - resultExtras, sync, sendingUser); + resultExtras, sync, sendingUser, processState); return true; } @@ -204,7 +207,8 @@ public abstract class ApplicationThreadNative extends Binder IBinder token = data.readStrongBinder(); ServiceInfo info = ServiceInfo.CREATOR.createFromParcel(data); CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data); - scheduleCreateService(token, info, compatInfo); + int processState = data.readInt(); + scheduleCreateService(token, info, compatInfo, processState); return true; } @@ -213,7 +217,8 @@ public abstract class ApplicationThreadNative extends Binder IBinder token = data.readStrongBinder(); Intent intent = Intent.CREATOR.createFromParcel(data); boolean rebind = data.readInt() != 0; - scheduleBindService(token, intent, rebind); + int processState = data.readInt(); + scheduleBindService(token, intent, rebind, processState); return true; } @@ -262,7 +267,7 @@ public abstract class ApplicationThreadNative extends Binder ? new ComponentName(data) : null; String profileName = data.readString(); ParcelFileDescriptor profileFd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; boolean autoStopProfiler = data.readInt() != 0; Bundle testArgs = data.readBundle(); IBinder binder = data.readStrongBinder(); @@ -333,7 +338,8 @@ public abstract class ApplicationThreadNative extends Binder final String proxy = data.readString(); final String port = data.readString(); final String exclList = data.readString(); - setHttpProxy(proxy, port, exclList); + final String pacFileUrl = data.readString(); + setHttpProxy(proxy, port, exclList, pacFileUrl); return true; } @@ -384,8 +390,9 @@ public abstract class ApplicationThreadNative extends Binder boolean ordered = data.readInt() != 0; boolean sticky = data.readInt() != 0; int sendingUser = data.readInt(); + int processState = data.readInt(); scheduleRegisteredReceiver(receiver, intent, - resultCode, dataStr, extras, ordered, sticky, sendingUser); + resultCode, dataStr, extras, ordered, sticky, sendingUser, processState); return true; } @@ -411,7 +418,7 @@ public abstract class ApplicationThreadNative extends Binder int profileType = data.readInt(); String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; profilerControl(start, path, fd, profileType); return true; } @@ -443,16 +450,6 @@ public abstract class ApplicationThreadNative extends Binder return true; } - case GET_MEMORY_INFO_TRANSACTION: - { - data.enforceInterface(IApplicationThread.descriptor); - Debug.MemoryInfo mi = new Debug.MemoryInfo(); - getMemoryInfo(mi); - reply.writeNoException(); - mi.writeToParcel(reply, 0); - return true; - } - case DISPATCH_PACKAGE_BROADCAST_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -476,7 +473,7 @@ public abstract class ApplicationThreadNative extends Binder boolean managed = data.readInt() != 0; String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 - ? data.readFileDescriptor() : null; + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; dumpHeap(managed, path, fd); return true; } @@ -523,13 +520,14 @@ public abstract class ApplicationThreadNative extends Binder { data.enforceInterface(IApplicationThread.descriptor); ParcelFileDescriptor fd = data.readFileDescriptor(); + Debug.MemoryInfo mi = Debug.MemoryInfo.CREATOR.createFromParcel(data); boolean checkin = data.readInt() != 0; - boolean all = data.readInt() != 0; + boolean dumpInfo = data.readInt() != 0; + boolean dumpDalvik = data.readInt() != 0; String[] args = data.readStringArray(); - Debug.MemoryInfo mi = null; if (fd != null) { try { - mi = dumpMemInfo(fd.getFileDescriptor(), checkin, all, args); + dumpMemInfo(fd.getFileDescriptor(), mi, checkin, dumpInfo, dumpDalvik, args); } finally { try { fd.close(); @@ -539,7 +537,6 @@ public abstract class ApplicationThreadNative extends Binder } } reply.writeNoException(); - mi.writeToParcel(reply, 0); return true; } @@ -592,13 +589,41 @@ public abstract class ApplicationThreadNative extends Binder return true; } - case REQUEST_ACTIVITY_EXTRAS_TRANSACTION: + case REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); IBinder activityToken = data.readStrongBinder(); IBinder requestToken = data.readStrongBinder(); int requestType = data.readInt(); - requestActivityExtras(activityToken, requestToken, requestType); + requestAssistContextExtras(activityToken, requestToken, requestType); + reply.writeNoException(); + return true; + } + + case SCHEDULE_TRANSLUCENT_CONVERSION_COMPLETE_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder token = data.readStrongBinder(); + boolean timeout = data.readInt() == 1; + scheduleTranslucentConversionComplete(token, timeout); + reply.writeNoException(); + return true; + } + + case SET_PROCESS_STATE_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + int state = data.readInt(); + setProcessState(state); + reply.writeNoException(); + return true; + } + + case SCHEDULE_INSTALL_PROVIDER_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + ProviderInfo provider = ProviderInfo.CREATOR.createFromParcel(data); + scheduleInstallProvider(provider); reply.writeNoException(); return true; } @@ -671,11 +696,12 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public final void scheduleResumeActivity(IBinder token, boolean isForward) + public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); + data.writeInt(procState); data.writeInt(isForward ? 1 : 0); mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); @@ -695,7 +721,7 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, - Bundle state, List pendingResults, + int procState, Bundle state, List pendingResults, List pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException { @@ -707,6 +733,7 @@ class ApplicationThreadProxy implements IApplicationThread { info.writeToParcel(data, 0); curConfig.writeToParcel(data, 0); compatInfo.writeToParcel(data, 0); + data.writeInt(procState); data.writeBundle(state); data.writeTypedList(pendingResults); data.writeTypedList(pendingNewIntents); @@ -772,7 +799,7 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String resultData, - Bundle map, boolean sync, int sendingUser) throws RemoteException { + Bundle map, boolean sync, int sendingUser, int processState) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); intent.writeToParcel(data, 0); @@ -783,6 +810,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeBundle(map); data.writeInt(sync ? 1 : 0); data.writeInt(sendingUser); + data.writeInt(processState); mRemote.transact(SCHEDULE_RECEIVER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -812,24 +840,26 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void scheduleCreateService(IBinder token, ServiceInfo info, - CompatibilityInfo compatInfo) throws RemoteException { + CompatibilityInfo compatInfo, int processState) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); info.writeToParcel(data, 0); compatInfo.writeToParcel(data, 0); + data.writeInt(processState); mRemote.transact(SCHEDULE_CREATE_SERVICE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } - public final void scheduleBindService(IBinder token, Intent intent, boolean rebind) - throws RemoteException { + public final void scheduleBindService(IBinder token, Intent intent, boolean rebind, + int processState) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); intent.writeToParcel(data, 0); data.writeInt(rebind ? 1 : 0); + data.writeInt(processState); mRemote.transact(SCHEDULE_BIND_SERVICE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -970,12 +1000,14 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public void setHttpProxy(String proxy, String port, String exclList) throws RemoteException { + public void setHttpProxy(String proxy, String port, String exclList, + String pacFileUrl) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeString(proxy); data.writeString(port); data.writeString(exclList); + data.writeString(pacFileUrl); mRemote.transact(SET_HTTP_PROXY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } @@ -1012,7 +1044,7 @@ class ApplicationThreadProxy implements IApplicationThread { public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode, String dataStr, Bundle extras, boolean ordered, - boolean sticky, int sendingUser) throws RemoteException { + boolean sticky, int sendingUser, int processState) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(receiver.asBinder()); @@ -1023,6 +1055,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeInt(ordered ? 1 : 0); data.writeInt(sticky ? 1 : 0); data.writeInt(sendingUser); + data.writeInt(processState); mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -1073,17 +1106,6 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public void getMemoryInfo(Debug.MemoryInfo outInfo) throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IApplicationThread.descriptor); - mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0); - reply.readException(); - outInfo.readFromParcel(reply); - data.recycle(); - reply.recycle(); - } - public void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); @@ -1159,22 +1181,21 @@ class ApplicationThreadProxy implements IApplicationThread { IBinder.FLAG_ONEWAY); } - public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, boolean all, - String[] args) throws RemoteException { + public void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin, + boolean dumpInfo, boolean dumpDalvik, String[] args) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeFileDescriptor(fd); + mem.writeToParcel(data, 0); data.writeInt(checkin ? 1 : 0); - data.writeInt(all ? 1 : 0); + data.writeInt(dumpInfo ? 1 : 0); + data.writeInt(dumpDalvik ? 1 : 0); data.writeStringArray(args); mRemote.transact(DUMP_MEM_INFO_TRANSACTION, data, reply, 0); reply.readException(); - Debug.MemoryInfo info = new Debug.MemoryInfo(); - info.readFromParcel(reply); data.recycle(); reply.recycle(); - return info; } public void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException { @@ -1195,6 +1216,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } + @Override public void unstableProviderDied(IBinder provider) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); @@ -1203,14 +1225,45 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public void requestActivityExtras(IBinder activityToken, IBinder requestToken, int requestType) - throws RemoteException { + @Override + public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken, + int requestType) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(activityToken); data.writeStrongBinder(requestToken); data.writeInt(requestType); - mRemote.transact(REQUEST_ACTIVITY_EXTRAS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); + mRemote.transact(REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + @Override + public void scheduleTranslucentConversionComplete(IBinder token, boolean timeout) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeInt(timeout ? 1 : 0); + mRemote.transact(SCHEDULE_TRANSLUCENT_CONVERSION_COMPLETE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); + data.recycle(); + } + + @Override + public void setProcessState(int state) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeInt(state); + mRemote.transact(SET_PROCESS_STATE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); + data.recycle(); + } + + @Override + public void scheduleInstallProvider(ProviderInfo provider) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + provider.writeToParcel(data, 0); + mRemote.transact(SCHEDULE_INSTALL_PROVIDER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } } diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 1b1d3418a5b1114deeaff3d1f545bc74aa5d305d..89ee145cbaf3baaf6a69c7d59bab869bd9420a6f 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -21,6 +21,7 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import android.util.LogWriter; +import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -583,8 +584,9 @@ final class BackStackRecord extends FragmentTransaction implements if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); - PrintWriter pw = new PrintWriter(logw); + PrintWriter pw = new FastPrintWriter(logw, false, 1024); dump(" ", null, pw, null); + pw.flush(); } mCommitted = true; if (mAddToBackStack) { @@ -691,8 +693,9 @@ final class BackStackRecord extends FragmentTransaction implements if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "popFromBackStack: " + this); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); - PrintWriter pw = new PrintWriter(logw); + PrintWriter pw = new FastPrintWriter(logw, false, 1024); dump(" ", null, pw, null); + pw.flush(); } bumpBackStackNesting(-1); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 3fc82faa90cd2301c6fd4651da25ee2659529df4..190ddb4b879aef073cb162ff872c413ebcf2ad5b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -16,6 +16,7 @@ package android.app; +import android.os.Build; import com.android.internal.policy.PolicyManager; import com.android.internal.util.Preconditions; @@ -46,9 +47,11 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; +import android.hardware.ConsumerIrManager; import android.hardware.ISerialManager; import android.hardware.SerialManager; import android.hardware.SystemSensorManager; +import android.hardware.camera2.CameraManager; import android.hardware.display.DisplayManager; import android.hardware.input.InputManager; import android.hardware.usb.IUsbManager; @@ -89,23 +92,29 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.SystemVibrator; import android.os.UserManager; +import android.os.storage.IMountService; import android.os.storage.StorageManager; +import android.print.IPrintManager; +import android.print.PrintManager; import android.telephony.TelephonyManager; import android.content.ClipboardManager; import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.Log; import android.util.Slog; -import android.view.CompatibilityInfoHolder; +import android.view.DisplayAdjustments; 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; import android.accounts.IAccountManager; import android.app.admin.DevicePolicyManager; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; import com.android.internal.os.IDropBoxManagerService; @@ -169,11 +178,14 @@ class ContextImpl extends Context { private final static String TAG = "ContextImpl"; private final static boolean DEBUG = false; - private static final HashMap sSharedPrefs = - new HashMap(); + /** + * Map from package name, to preference name, to cached preferences. + */ + private static ArrayMap> sSharedPrefs; /*package*/ LoadedApk mPackageInfo; private String mBasePackageName; + private String mOpPackageName; private Resources mResources; /*package*/ ActivityThread mMainThread; private Context mOuterContext; @@ -186,19 +198,30 @@ class ContextImpl extends Context { private Context mReceiverRestrictedContext = null; private boolean mRestricted; private UserHandle mUser; + private ResourcesManager mResourcesManager; private final Object mSync = new Object(); + @GuardedBy("mSync") private File mDatabasesDir; + @GuardedBy("mSync") private File mPreferencesDir; + @GuardedBy("mSync") private File mFilesDir; + @GuardedBy("mSync") private File mCacheDir; - private File mObbDir; - private File mExternalFilesDir; - private File mExternalCacheDir; + + @GuardedBy("mSync") + private File[] mExternalObbDirs; + @GuardedBy("mSync") + private File[] mExternalFilesDirs; + @GuardedBy("mSync") + private File[] mExternalCacheDirs; private static final String[] EMPTY_FILE_LIST = {}; + final private DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments(); + /** * Override this class when the system service constructor needs a * ContextImpl. Else, use StaticServiceFetcher below. @@ -288,6 +311,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); @@ -300,11 +328,11 @@ class ContextImpl extends Context { return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler()); }}); - registerService(ALARM_SERVICE, new StaticServiceFetcher() { - public Object createStaticService() { + registerService(ALARM_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(ALARM_SERVICE); IAlarmManager service = IAlarmManager.Stub.asInterface(b); - return new AlarmManager(service); + return new AlarmManager(service, ctx); }}); registerService(AUDIO_SERVICE, new ServiceFetcher() { @@ -513,12 +541,16 @@ class ContextImpl extends Context { }}); registerService(WINDOW_SERVICE, new ServiceFetcher() { + Display mDefaultDisplay; public Object getService(ContextImpl ctx) { Display display = ctx.mDisplay; if (display == null) { - DisplayManager dm = (DisplayManager)ctx.getOuterContext().getSystemService( - Context.DISPLAY_SERVICE); - display = dm.getDisplay(Display.DEFAULT_DISPLAY); + if (mDefaultDisplay == null) { + DisplayManager dm = (DisplayManager)ctx.getOuterContext(). + getSystemService(Context.DISPLAY_SERVICE); + mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY); + } + display = mDefaultDisplay; } return new WindowManagerImpl(display); }}); @@ -536,6 +568,25 @@ class ContextImpl extends Context { IAppOpsService service = IAppOpsService.Stub.asInterface(b); return new AppOpsManager(ctx, service); }}); + + registerService(CAMERA_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new CameraManager(ctx); + } + }); + + registerService(PRINT_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE); + IPrintManager service = IPrintManager.Stub.asInterface(iBinder); + return new PrintManager(ctx.getOuterContext(), service, UserHandle.myUserId(), + UserHandle.getAppId(Process.myUid())); + }}); + + registerService(CONSUMER_IR_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new ConsumerIrManager(ctx); + }}); } static ContextImpl getImpl(Context context) { @@ -636,6 +687,12 @@ class ContextImpl extends Context { return mBasePackageName != null ? mBasePackageName : getPackageName(); } + /** @hide */ + @Override + public String getOpPackageName() { + return mOpPackageName != null ? mOpPackageName : getBasePackageName(); + } + @Override public ApplicationInfo getApplicationInfo() { if (mPackageInfo != null) { @@ -667,12 +724,33 @@ class ContextImpl extends Context { @Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; - synchronized (sSharedPrefs) { - sp = sSharedPrefs.get(name); + synchronized (ContextImpl.class) { + if (sSharedPrefs == null) { + sSharedPrefs = new ArrayMap>(); + } + + final String packageName = getPackageName(); + ArrayMap packagePrefs = sSharedPrefs.get(packageName); + if (packagePrefs == null) { + packagePrefs = new ArrayMap(); + sSharedPrefs.put(packageName, packagePrefs); + } + + // At least one application in the world actually passes in a null + // name. This happened to work because when we generated the file name + // we would stringify it to "null.xml". Nice. + if (mPackageInfo.getApplicationInfo().targetSdkVersion < + Build.VERSION_CODES.KITKAT) { + if (name == null) { + name = "null"; + } + } + + sp = packagePrefs.get(name); if (sp == null) { File prefsFile = getSharedPrefsFile(name); sp = new SharedPreferencesImpl(prefsFile, mode); - sSharedPrefs.put(name, sp); + packagePrefs.put(name, sp); return sp; } } @@ -739,6 +817,10 @@ class ContextImpl extends Context { } if (!mFilesDir.exists()) { if(!mFilesDir.mkdirs()) { + if (mFilesDir.exists()) { + // spurious failure; probably racing with another process for this app + return mFilesDir; + } Log.w(TAG, "Unable to create files directory " + mFilesDir.getPath()); return null; } @@ -753,44 +835,43 @@ class ContextImpl extends Context { @Override public File getExternalFilesDir(String type) { + // Operates on primary external storage + return getExternalFilesDirs(type)[0]; + } + + @Override + public File[] getExternalFilesDirs(String type) { synchronized (mSync) { - if (mExternalFilesDir == null) { - mExternalFilesDir = Environment.getExternalStorageAppFilesDirectory( - getPackageName()); + if (mExternalFilesDirs == null) { + mExternalFilesDirs = Environment.buildExternalStorageAppFilesDirs(getPackageName()); } - if (!mExternalFilesDir.exists()) { - try { - (new File(Environment.getExternalStorageAndroidDataDir(), - ".nomedia")).createNewFile(); - } catch (IOException e) { - } - if (!mExternalFilesDir.mkdirs()) { - Log.w(TAG, "Unable to create external files directory"); - return null; - } - } - if (type == null) { - return mExternalFilesDir; - } - File dir = new File(mExternalFilesDir, type); - if (!dir.exists()) { - if (!dir.mkdirs()) { - Log.w(TAG, "Unable to create external media directory " + dir); - return null; - } + + // Splice in requested type, if any + File[] dirs = mExternalFilesDirs; + if (type != null) { + dirs = Environment.buildPaths(dirs, type); } - return dir; + + // Create dirs if needed + return ensureDirsExistOrFilter(dirs); } } @Override public File getObbDir() { + // Operates on primary external storage + return getObbDirs()[0]; + } + + @Override + public File[] getObbDirs() { synchronized (mSync) { - if (mObbDir == null) { - mObbDir = Environment.getExternalStorageAppObbDirectory( - getPackageName()); + if (mExternalObbDirs == null) { + mExternalObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); } - return mObbDir; + + // Create dirs if needed + return ensureDirsExistOrFilter(mExternalObbDirs); } } @@ -802,6 +883,10 @@ class ContextImpl extends Context { } if (!mCacheDir.exists()) { if(!mCacheDir.mkdirs()) { + if (mCacheDir.exists()) { + // spurious failure; probably racing with another process for this app + return mCacheDir; + } Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath()); return null; } @@ -816,23 +901,19 @@ class ContextImpl extends Context { @Override public File getExternalCacheDir() { + // Operates on primary external storage + return getExternalCacheDirs()[0]; + } + + @Override + public File[] getExternalCacheDirs() { synchronized (mSync) { - if (mExternalCacheDir == null) { - mExternalCacheDir = Environment.getExternalStorageAppCacheDirectory( - getPackageName()); - } - if (!mExternalCacheDir.exists()) { - try { - (new File(Environment.getExternalStorageAndroidDataDir(), - ".nomedia")).createNewFile(); - } catch (IOException e) { - } - if (!mExternalCacheDir.mkdirs()) { - Log.w(TAG, "Unable to create external cache directory"); - return null; - } + if (mExternalCacheDirs == null) { + mExternalCacheDirs = Environment.buildExternalStorageAppCacheDirs(getPackageName()); } - return mExternalCacheDir; + + // Create dirs if needed + return ensureDirsExistOrFilter(mExternalCacheDirs); } } @@ -1380,21 +1461,39 @@ class ContextImpl extends Context { } } + private void validateServiceIntent(Intent service) { + if (service.getComponent() == null && service.getPackage() == null) { + if (true || getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.KITKAT) { + Log.w(TAG, "Implicit intents with startService are not safe: " + service + + " " + Debug.getCallers(2, 3)); + //IllegalArgumentException ex = new IllegalArgumentException( + // "Service Intent must be explicit: " + service); + //Log.e(TAG, "This will become an error", ex); + //throw ex; + } + } + } + @Override public ComponentName startService(Intent service) { warnIfCallingFromSystemProcess(); - return startServiceAsUser(service, mUser); + return startServiceCommon(service, mUser); } @Override public boolean stopService(Intent service) { warnIfCallingFromSystemProcess(); - return stopServiceAsUser(service, mUser); + return stopServiceCommon(service, mUser); } @Override public ComponentName startServiceAsUser(Intent service, UserHandle user) { + return startServiceCommon(service, user); + } + + private ComponentName startServiceCommon(Intent service, UserHandle user) { try { + validateServiceIntent(service); service.prepareToLeaveProcess(); ComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, @@ -1418,7 +1517,12 @@ class ContextImpl extends Context { @Override public boolean stopServiceAsUser(Intent service, UserHandle user) { + return stopServiceCommon(service, user); + } + + private boolean stopServiceCommon(Intent service, UserHandle user) { try { + validateServiceIntent(service); service.prepareToLeaveProcess(); int res = ActivityManagerNative.getDefault().stopService( mMainThread.getApplicationThread(), service, @@ -1437,13 +1541,18 @@ class ContextImpl extends Context { public boolean bindService(Intent service, ServiceConnection conn, int flags) { warnIfCallingFromSystemProcess(); - return bindServiceAsUser(service, conn, flags, Process.myUserHandle()); + return bindServiceCommon(service, conn, flags, Process.myUserHandle()); } /** @hide */ @Override public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) { + return bindServiceCommon(service, conn, flags, user); + } + + private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, + UserHandle user) { IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); @@ -1454,6 +1563,7 @@ class ContextImpl extends Context { } else { throw new RuntimeException("Not supported in system context"); } + validateServiceIntent(service); try { IBinder token = getActivityToken(); if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null @@ -1746,6 +1856,11 @@ class ContextImpl extends Context { message); } + /** + * Logs a warning if the system process directly called a method such as + * {@link #startService(Intent)} instead of {@link #startServiceAsUser(Intent, UserHandle)}. + * The "AsUser" variants allow us to properly enforce the user's restrictions. + */ private void warnIfCallingFromSystemProcess() { if (Process.myUid() == Process.SYSTEM_UID) { Slog.w(TAG, "Calling a method in the system process without a qualified user: " @@ -1795,10 +1910,9 @@ class ContextImpl extends Context { ContextImpl c = new ContextImpl(); c.init(mPackageInfo, null, mMainThread); - c.mResources = mMainThread.getTopLevelResources( - mPackageInfo.getResDir(), - getDisplayId(), overrideConfiguration, - mResources.getCompatibilityInfo()); + c.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), + getDisplayId(), overrideConfiguration, mResources.getCompatibilityInfo(), + mActivityToken); return c; } @@ -1809,17 +1923,13 @@ class ContextImpl extends Context { } int displayId = display.getDisplayId(); - CompatibilityInfo ci = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; - CompatibilityInfoHolder cih = getCompatibilityInfo(displayId); - if (cih != null) { - ci = cih.get(); - } ContextImpl context = new ContextImpl(); context.init(mPackageInfo, null, mMainThread); context.mDisplay = display; - context.mResources = mMainThread.getTopLevelResources( - mPackageInfo.getResDir(), displayId, null, ci); + DisplayAdjustments daj = getDisplayAdjustments(displayId); + context.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), + displayId, null, daj.getCompatibilityInfo(), null); return context; } @@ -1833,8 +1943,8 @@ class ContextImpl extends Context { } @Override - public CompatibilityInfoHolder getCompatibilityInfo(int displayId) { - return displayId == Display.DEFAULT_DISPLAY ? mPackageInfo.mCompatibilityInfo : null; + public DisplayAdjustments getDisplayAdjustments(int displayId) { + return mDisplayAdjustments; } private File getDataDirFile() { @@ -1880,12 +1990,14 @@ class ContextImpl extends Context { public ContextImpl(ContextImpl context) { mPackageInfo = context.mPackageInfo; mBasePackageName = context.mBasePackageName; + mOpPackageName = context.mOpPackageName; mResources = context.mResources; mMainThread = context.mMainThread; mContentResolver = context.mContentResolver; mUser = context.mUser; mDisplay = context.mDisplay; mOuterContext = this; + mDisplayAdjustments.setCompatibilityInfo(mPackageInfo.getCompatibilityInfo()); } final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) { @@ -1895,19 +2007,44 @@ class ContextImpl extends Context { final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread, Resources container, String basePackageName, UserHandle user) { mPackageInfo = packageInfo; - mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName; + if (basePackageName != null) { + mBasePackageName = mOpPackageName = basePackageName; + } else { + mBasePackageName = packageInfo.mPackageName; + ApplicationInfo ainfo = packageInfo.getApplicationInfo(); + if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) { + // Special case: system components allow themselves to be loaded in to other + // processes. For purposes of app ops, we must then consider the context as + // belonging to the package of this process, not the system itself, otherwise + // the package+uid verifications in app ops will fail. + mOpPackageName = ActivityThread.currentPackageName(); + } else { + mOpPackageName = mBasePackageName; + } + } mResources = mPackageInfo.getResources(mainThread); - - if (mResources != null && container != null - && container.getCompatibilityInfo().applicationScale != - mResources.getCompatibilityInfo().applicationScale) { + mResourcesManager = ResourcesManager.getInstance(); + + CompatibilityInfo compatInfo = + container == null ? null : container.getCompatibilityInfo(); + if (mResources != null && + ((compatInfo != null && compatInfo.applicationScale != + mResources.getCompatibilityInfo().applicationScale) + || activityToken != null)) { if (DEBUG) { Log.d(TAG, "loaded context has different scaling. Using container's" + " compatiblity info:" + container.getDisplayMetrics()); } - mResources = mainThread.getTopLevelResources( - mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY, - null, container.getCompatibilityInfo()); + if (compatInfo == null) { + compatInfo = packageInfo.getCompatibilityInfo(); + } + mDisplayAdjustments.setCompatibilityInfo(compatInfo); + mDisplayAdjustments.setActivityToken(activityToken); + mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), + Display.DEFAULT_DISPLAY, null, compatInfo, activityToken); + } else { + mDisplayAdjustments.setCompatibilityInfo(packageInfo.getCompatibilityInfo()); + mDisplayAdjustments.setActivityToken(activityToken); } mMainThread = mainThread; mActivityToken = activityToken; @@ -1918,6 +2055,7 @@ class ContextImpl extends Context { final void init(Resources resources, ActivityThread mainThread, UserHandle user) { mPackageInfo = null; mBasePackageName = null; + mOpPackageName = null; mResources = resources; mMainThread = mainThread; mContentResolver = new ApplicationContentResolver(this, mainThread, user); @@ -2001,6 +2139,39 @@ class ContextImpl extends Context { "File " + name + " contains a path separator"); } + /** + * Ensure that given directories exist, trying to create them if missing. If + * unable to create, they are filtered by replacing with {@code null}. + */ + private File[] ensureDirsExistOrFilter(File[] dirs) { + File[] result = new File[dirs.length]; + for (int i = 0; i < dirs.length; i++) { + File dir = dirs[i]; + if (!dir.exists()) { + if (!dir.mkdirs()) { + // recheck existence in case of cross-process race + if (!dir.exists()) { + // Failing to mkdir() may be okay, since we might not have + // enough permissions; ask vold to create on our behalf. + final IMountService mount = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); + int res = -1; + try { + res = mount.mkdirs(getPackageName(), dir.getAbsolutePath()); + } catch (RemoteException e) { + } + if (res != 0) { + Log.w(TAG, "Failed to ensure directory: " + dir); + dir = null; + } + } + } + } + result[i] = dir; + } + return result; + } + // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- @@ -2045,5 +2216,10 @@ class ContextImpl extends Context { public void unstableProviderDied(IContentProvider icp) { mMainThread.handleUnstableProviderDied(icp.asBinder(), true); } + + @Override + public void appNotRespondingViaProvider(IContentProvider icp) { + mMainThread.appNotRespondingViaProvider(icp.asBinder()); + } } } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index b3d99c5242f0e0d6ff868e202055c4eeed09e0c1..cda2c5f3b7c5dc98f7744a6a0a4ac29b9e3f80d0 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -16,6 +16,8 @@ package android.app; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import com.android.internal.app.ActionBarImpl; import com.android.internal.policy.PolicyManager; @@ -264,6 +266,9 @@ public class Dialog implements DialogInterface, Window.Callback, mDecor = mWindow.getDecorView(); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { + final ApplicationInfo info = mContext.getApplicationInfo(); + mWindow.setDefaultIcon(info.icon); + mWindow.setDefaultLogo(info.logo); mActionBar = new ActionBarImpl(this); } @@ -301,6 +306,7 @@ public class Dialog implements DialogInterface, Window.Callback, * method to do cleanup when the dialog is dismissed, instead implement * that in {@link #onStop}. */ + @Override public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) { dismissDialog(); @@ -320,7 +326,7 @@ public class Dialog implements DialogInterface, Window.Callback, } try { - mWindowManager.removeView(mDecor); + mWindowManager.removeViewImmediate(mDecor); } finally { if (mActionMode != null) { mActionMode.finish(); @@ -329,7 +335,7 @@ public class Dialog implements DialogInterface, Window.Callback, mWindow.closeAllPanels(); onStop(); mShowing = false; - + sendDismissMessage(); } } diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 165c3dbd3e0d0419599fa7715715b4a3a8866799..b741cc5c85b35c64e6089f1f6e9eec2e6b532dd5 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -16,6 +16,8 @@ package android.app; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -149,6 +151,11 @@ public class DownloadManager { */ public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI; + /** + * @hide + */ + public final static String COLUMN_ALLOW_WRITE = Downloads.Impl.COLUMN_ALLOW_WRITE; + /** * Value of {@link #COLUMN_STATUS} when the download is waiting to start. */ @@ -262,18 +269,21 @@ public class DownloadManager { /** * Broadcast intent action sent by the download manager when a download completes. */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE"; /** * Broadcast intent action sent by the download manager when the user clicks on a running * download, either from a system notification or from the downloads UI. */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public final static String ACTION_NOTIFICATION_CLICKED = "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"; /** * Intent action to launch an activity to display all downloads. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS"; /** @@ -315,6 +325,7 @@ public class DownloadManager { Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + COLUMN_TOTAL_SIZE_BYTES, Downloads.Impl.COLUMN_LAST_MODIFICATION + " AS " + COLUMN_LAST_MODIFIED_TIMESTAMP, Downloads.Impl.COLUMN_CURRENT_BYTES + " AS " + COLUMN_BYTES_DOWNLOADED_SO_FAR, + Downloads.Impl.COLUMN_ALLOW_WRITE, /* add the following 'computed' columns to the cursor. * they are not 'returned' by the database, but their inclusion * eliminates need to have lot of methods in CursorTranslator @@ -1185,6 +1196,14 @@ public class DownloadManager { public long addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification) { + return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, + length, showNotification, false); + } + + /** {@hide} */ + public long addCompletedDownload(String title, String description, + boolean isMediaScannerScannable, String mimeType, String path, long length, + boolean showNotification, boolean allowWrite) { // make sure the input args are non-null/non-zero validateArgumentIsNonEmpty("title", title); validateArgumentIsNonEmpty("description", description); @@ -1210,12 +1229,14 @@ public class DownloadManager { Request.SCANNABLE_VALUE_NO); values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ? Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN); + values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0); Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); if (downloadUri == null) { return -1; } return Long.parseLong(downloadUri.getLastPathSegment()); } + private static final String NON_DOWNLOADMANAGER_DOWNLOAD = "non-dwnldmngr-download-dont-retry2download"; @@ -1227,8 +1248,10 @@ public class DownloadManager { /** * Get the DownloadProvider URI for the download with the given ID. + * + * @hide */ - Uri getDownloadUri(long id) { + public Uri getDownloadUri(long id) { return ContentUris.withAppendedId(mBaseUri, id); } diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index b6aeb84bdb540d1cba21df72bf935d351c59aee5..d626e5f54aeecaf834df9d3e2bd9bb66c943061c 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -26,10 +26,12 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DebugUtils; import android.util.Log; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; @@ -43,7 +45,6 @@ import android.widget.AdapterView; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.HashMap; final class FragmentState implements Parcelable { final String mClassName; @@ -342,8 +343,8 @@ final class FragmentState implements Parcelable { * the activity UI was in. */ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener { - private static final HashMap> sClassMap = - new HashMap>(); + private static final ArrayMap> sClassMap = + new ArrayMap>(); static final int INVALID_STATE = -1; // Invalid state used as a null value. static final int INITIALIZING = 0; // Not yet created. @@ -580,6 +581,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (clazz == null) { // Class not found in the cache, see if it's real, and try to add it clazz = context.getClassLoader().loadClass(fname); + if (!Fragment.class.isAssignableFrom(clazz)) { + throw new InstantiationException("Trying to instantiate a class " + fname + + " that is not a Fragment", new ClassCastException()); + } sClassMap.put(fname, clazz); } Fragment f = (Fragment)clazz.newInstance(); diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index d564b4c4be172f517f0675c9ee8d63d98b566233..bf2a6292ce665ba8b394c5a5c2f67925e13e1e5d 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -31,11 +31,13 @@ import android.util.DebugUtils; import android.util.Log; import android.util.LogWriter; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -445,12 +447,13 @@ final class FragmentManagerImpl extends FragmentManager { private void throwException(RuntimeException ex) { Log.e(TAG, ex.getMessage()); LogWriter logw = new LogWriter(Log.ERROR, TAG); - PrintWriter pw = new PrintWriter(logw); + PrintWriter pw = new FastPrintWriter(logw, false, 1024); if (mActivity != null) { Log.e(TAG, "Activity state:"); try { mActivity.dump(" ", null, pw, new String[] { }); } catch (Exception e) { + pw.flush(); Log.e(TAG, "Failed dumping state", e); } } else { @@ -458,9 +461,11 @@ final class FragmentManagerImpl extends FragmentManager { try { dump(" ", null, pw, new String[] { }); } catch (Exception e) { + pw.flush(); Log.e(TAG, "Failed dumping state", e); } } + pw.flush(); throw ex; } @@ -1324,12 +1329,19 @@ final class FragmentManagerImpl extends FragmentManager { } } + /** + * Adds an action to the queue of pending actions. + * + * @param action the action to add + * @param allowStateLoss whether to allow loss of state information + * @throws IllegalStateException if the activity has been destroyed + */ public void enqueueAction(Runnable action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { - if (mActivity == null) { + if (mDestroyed || mActivity == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { @@ -1806,8 +1818,9 @@ final class FragmentManagerImpl extends FragmentManager { Log.v(TAG, "restoreAllState: back stack #" + i + " (index " + bse.mIndex + "): " + bse); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); - PrintWriter pw = new PrintWriter(logw); + PrintWriter pw = new FastPrintWriter(logw, false, 1024); bse.dump(" ", pw, false); + pw.flush(); } mBackStack.add(bse); if (bse.mIndex >= 0) { diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index a21caee7f092fdd15f153ac8acdf6d2a0d068684..77c2ea0c169479c9023fbba3554cd7a5a4fb81c6 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -16,6 +16,9 @@ package android.app; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.RunningServiceInfo; +import android.app.ActivityManager.StackBoxInfo; import android.content.ComponentName; import android.content.ContentProviderNative; import android.content.IContentProvider; @@ -24,9 +27,11 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.UriPermission; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; +import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; @@ -99,19 +104,26 @@ public interface IActivityManager extends IInterface { public void activityDestroyed(IBinder token) throws RemoteException; public String getCallingPackage(IBinder token) throws RemoteException; public ComponentName getCallingActivity(IBinder token) throws RemoteException; - public List getTasks(int maxNum, int flags, + public List getTasks(int maxNum, int flags, IThumbnailReceiver receiver) throws RemoteException; public List getRecentTasks(int maxNum, int flags, int userId) throws RemoteException; public ActivityManager.TaskThumbnails getTaskThumbnails(int taskId) throws RemoteException; public Bitmap getTaskTopThumbnail(int taskId) throws RemoteException; - public List getServices(int maxNum, int flags) throws RemoteException; + public List getServices(int maxNum, int flags) throws RemoteException; public List getProcessesInErrorState() throws RemoteException; public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException; public void moveTaskToBack(int task) throws RemoteException; public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; public void moveTaskBackwards(int task) throws RemoteException; + public int createStack(int taskId, int relativeStackBoxId, int position, float weight) + throws RemoteException; + public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException; + public void resizeStackBox(int stackBoxId, float weight) throws RemoteException; + public List getStackBoxes() throws RemoteException; + public StackBoxInfo getStackBoxInfo(int stackBoxId) throws RemoteException; + public void setFocusedStack(int stackId) throws RemoteException; public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; /* oneway */ public void reportThumbnail(IBinder token, @@ -127,6 +139,7 @@ public interface IActivityManager extends IInterface { public boolean refContentProvider(IBinder connection, int stableDelta, int unstableDelta) throws RemoteException; public void unstableProviderDied(IBinder connection) throws RemoteException; + public void appNotRespondingViaProvider(IBinder connection) throws RemoteException; public PendingIntent getRunningServiceControlPanel(ComponentName service) throws RemoteException; public ComponentName startService(IApplicationThread caller, Intent service, @@ -149,14 +162,14 @@ public interface IActivityManager extends IInterface { public void serviceDoneExecuting(IBinder token, int type, int startId, int res) throws RemoteException; public IBinder peekService(Intent service, String resolvedType) throws RemoteException; - + public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode) throws RemoteException; public void clearPendingBackup() throws RemoteException; public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException; public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException; public void killApplicationProcess(String processName, int uid) throws RemoteException; - + public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, IUiAutomationConnection connection, int userId) throws RemoteException; @@ -168,7 +181,7 @@ public interface IActivityManager extends IInterface { public void setRequestedOrientation(IBinder token, int requestedOrientation) throws RemoteException; public int getRequestedOrientation(IBinder token) throws RemoteException; - + public ComponentName getActivityClassForToken(IBinder token) throws RemoteException; public String getPackageForToken(IBinder token) throws RemoteException; @@ -181,16 +194,16 @@ public interface IActivityManager extends IInterface { final IPackageDataObserver observer, int userId) throws RemoteException; public String getPackageForIntentSender(IIntentSender sender) throws RemoteException; public int getUidForIntentSender(IIntentSender sender) throws RemoteException; - + public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, boolean requireFull, String name, String callerPackage) throws RemoteException; public void setProcessLimit(int max) throws RemoteException; public int getProcessLimit() throws RemoteException; - + public void setProcessForeground(IBinder token, int pid, boolean isForeground) throws RemoteException; - + public int checkPermission(String permission, int pid, int uid) throws RemoteException; @@ -200,7 +213,11 @@ public interface IActivityManager extends IInterface { Uri uri, int mode) throws RemoteException; public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode) throws RemoteException; - + public void takePersistableUriPermission(Uri uri, int modeFlags) throws RemoteException; + public void releasePersistableUriPermission(Uri uri, int modeFlags) throws RemoteException; + public ParceledListSlice getPersistedUriPermissions( + String packageName, boolean incoming) throws RemoteException; + public void showWaitingForDebugger(IApplicationThread who, boolean waiting) throws RemoteException; @@ -248,7 +265,7 @@ public interface IActivityManager extends IInterface { StrictMode.ViolationInfo crashInfo) throws RemoteException; /* - * This will deliver the specified signal to all the persistent processes. Currently only + * This will deliver the specified signal to all the persistent processes. Currently only * SIGUSR1 is delivered. All others are ignored. */ public void signalPersistentProcesses(int signal) throws RemoteException; @@ -274,7 +291,8 @@ public interface IActivityManager extends IInterface { public void stopAppSwitches() throws RemoteException; public void resumeAppSwitches() throws RemoteException; - public void killApplicationWithAppId(String pkg, int appid) throws RemoteException; + public void killApplicationWithAppId(String pkg, int appid, String reason) + throws RemoteException; public void closeSystemDialogs(String reason) throws RemoteException; @@ -290,6 +308,10 @@ public interface IActivityManager extends IInterface { public void finishHeavyWeightApp() throws RemoteException; + public boolean convertFromTranslucent(IBinder token) throws RemoteException; + public boolean convertToTranslucent(IBinder token) throws RemoteException; + public void notifyActivityDrawn(IBinder token) throws RemoteException; + public void setImmersive(IBinder token, boolean immersive) throws RemoteException; public boolean isImmersive(IBinder token) throws RemoteException; public boolean isTopActivityImmersive() throws RemoteException; @@ -369,16 +391,23 @@ public interface IActivityManager extends IInterface { public void requestBugReport() throws RemoteException; - public long inputDispatchingTimedOut(int pid, boolean aboveSystem) throws RemoteException; + public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) + throws RemoteException; - public Bundle getTopActivityExtras(int requestType) throws RemoteException; + public Bundle getAssistContextExtras(int requestType) throws RemoteException; - public void reportTopActivityExtras(IBinder token, Bundle extras) throws RemoteException; + public void reportAssistContextExtras(IBinder token, Bundle extras) throws RemoteException; public void killUid(int uid, String reason) throws RemoteException; public void hang(IBinder who, boolean allowRestart) throws RemoteException; + public void reportActivityFullyDrawn(IBinder token) throws RemoteException; + + public void restart() throws RemoteException; + + public void performIdleMaintenance() throws RemoteException; + /* * Private non-Binder interfaces */ @@ -395,10 +424,12 @@ public interface IActivityManager extends IInterface { info = _info; } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { info.writeToParcel(dest, 0); if (provider != null) { @@ -412,10 +443,12 @@ public interface IActivityManager extends IInterface { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public ContentProviderHolder createFromParcel(Parcel source) { return new ContentProviderHolder(source); } + @Override public ContentProviderHolder[] newArray(int size) { return new ContentProviderHolder[size]; } @@ -441,10 +474,12 @@ public interface IActivityManager extends IInterface { public WaitResult() { } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(result); dest.writeInt(timeout ? 1 : 0); @@ -455,10 +490,12 @@ public interface IActivityManager extends IInterface { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public WaitResult createFromParcel(Parcel source) { return new WaitResult(source); } + @Override public WaitResult[] newArray(int size) { return new WaitResult[size]; } @@ -471,7 +508,7 @@ public interface IActivityManager extends IInterface { thisTime = source.readLong(); totalTime = source.readLong(); } - }; + } String descriptor = "android.app.IActivityManager"; @@ -635,10 +672,26 @@ public interface IActivityManager extends IInterface { int INPUT_DISPATCHING_TIMED_OUT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+158; int CLEAR_PENDING_BACKUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+159; int GET_INTENT_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+160; - int GET_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+161; - int REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+162; + int GET_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+161; + int REPORT_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+162; int GET_LAUNCHED_FROM_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+163; int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164; int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165; int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166; + int CREATE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167; + int MOVE_TASK_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+168; + int RESIZE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+169; + int GET_STACK_BOXES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+170; + int SET_FOCUSED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+171; + int GET_STACK_BOX_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+172; + int CONVERT_FROM_TRANSLUCENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+173; + int CONVERT_TO_TRANSLUCENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+174; + int NOTIFY_ACTIVITY_DRAWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+175; + int REPORT_ACTIVITY_FULLY_DRAWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+176; + int RESTART_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+177; + int PERFORM_IDLE_MAINTENANCE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+178; + int TAKE_PERSISTABLE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+179; + int RELEASE_PERSISTABLE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+180; + int GET_PERSISTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+181; + int APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+182; } diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl index edb40ed77f51e3580227ccde749d4bc4a44b73c4..8476609c83b97928ab4903fd8e3b0aa99660f919 100644 --- a/core/java/android/app/IAlarmManager.aidl +++ b/core/java/android/app/IAlarmManager.aidl @@ -17,6 +17,7 @@ package android.app; import android.app.PendingIntent; +import android.os.WorkSource; /** * System private API for talking with the alarm manager service. @@ -24,9 +25,9 @@ import android.app.PendingIntent; * {@hide} */ interface IAlarmManager { - void set(int type, long triggerAtTime, in PendingIntent operation); - void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); - void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); + /** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */ + void set(int type, long triggerAtTime, long windowLength, + long interval, in PendingIntent operation, in WorkSource workSource); void setTime(long millis); void setTimeZone(String zone); void remove(in PendingIntent operation); diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 3189b31af1fa4622008663ef8f7de1b52e8f756b..d0cc1bbc9c2b782adea1ea7a2eb479a482b6cfe7 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -50,11 +50,12 @@ public interface IApplicationThread extends IInterface { int configChanges) throws RemoteException; void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException; void scheduleSleeping(IBinder token, boolean sleeping) throws RemoteException; - void scheduleResumeActivity(IBinder token, boolean isForward) throws RemoteException; + void scheduleResumeActivity(IBinder token, int procState, boolean isForward) + throws RemoteException; void scheduleSendResult(IBinder token, List results) throws RemoteException; void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, - Bundle state, List pendingResults, + int procState, Bundle state, List pendingResults, List pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException; @@ -66,7 +67,7 @@ public interface IApplicationThread extends IInterface { int configChanges) throws RemoteException; void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, boolean sync, - int sendingUser) throws RemoteException; + int sendingUser, int processState) throws RemoteException; static final int BACKUP_MODE_INCREMENTAL = 0; static final int BACKUP_MODE_FULL = 1; static final int BACKUP_MODE_RESTORE = 2; @@ -76,9 +77,9 @@ public interface IApplicationThread extends IInterface { void scheduleDestroyBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo) throws RemoteException; void scheduleCreateService(IBinder token, ServiceInfo info, - CompatibilityInfo compatInfo) throws RemoteException; + CompatibilityInfo compatInfo, int processState) throws RemoteException; void scheduleBindService(IBinder token, - Intent intent, boolean rebind) throws RemoteException; + Intent intent, boolean rebind, int processState) throws RemoteException; void scheduleUnbindService(IBinder token, Intent intent) throws RemoteException; void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, @@ -100,7 +101,8 @@ public interface IApplicationThread extends IInterface { void scheduleConfigurationChanged(Configuration config) throws RemoteException; void updateTimeZone() throws RemoteException; void clearDnsCache() throws RemoteException; - void setHttpProxy(String proxy, String port, String exclList) throws RemoteException; + void setHttpProxy(String proxy, String port, String exclList, + String pacFileUrl) throws RemoteException; void processInBackground() throws RemoteException; void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) throws RemoteException; @@ -108,7 +110,7 @@ public interface IApplicationThread extends IInterface { throws RemoteException; void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, boolean ordered, - boolean sticky, int sendingUser) throws RemoteException; + boolean sticky, int sendingUser, int processState) throws RemoteException; void scheduleLowMemory() throws RemoteException; void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException; void profilerControl(boolean start, String path, ParcelFileDescriptor fd, int profileType) @@ -116,7 +118,6 @@ public interface IApplicationThread extends IInterface { void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) throws RemoteException; void setSchedulingGroup(int group) throws RemoteException; - void getMemoryInfo(Debug.MemoryInfo outInfo) throws RemoteException; static final int PACKAGE_REMOVED = 0; static final int EXTERNAL_STORAGE_UNAVAILABLE = 1; void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException; @@ -126,13 +127,17 @@ public interface IApplicationThread extends IInterface { void setCoreSettings(Bundle coreSettings) throws RemoteException; void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) throws RemoteException; void scheduleTrimMemory(int level) throws RemoteException; - Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, boolean all, - String[] args) throws RemoteException; + void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin, boolean dumpInfo, + boolean dumpDalvik, String[] args) throws RemoteException; void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException; void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException; void unstableProviderDied(IBinder provider) throws RemoteException; - void requestActivityExtras(IBinder activityToken, IBinder requestToken, int requestType) + void requestAssistContextExtras(IBinder activityToken, IBinder requestToken, int requestType) + throws RemoteException; + void scheduleTranslucentConversionComplete(IBinder token, boolean timeout) throws RemoteException; + void setProcessState(int state) throws RemoteException; + void scheduleInstallProvider(ProviderInfo provider) throws RemoteException; String descriptor = "android.app.IApplicationThread"; @@ -166,7 +171,7 @@ public interface IApplicationThread extends IInterface { int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; - int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; + int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33; int SCHEDULE_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34; @@ -182,5 +187,8 @@ public interface IApplicationThread extends IInterface { int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44; int DUMP_DB_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45; int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46; - int REQUEST_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47; + int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47; + int SCHEDULE_TRANSLUCENT_CONVERSION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+48; + int SET_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+49; + int SCHEDULE_INSTALL_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+50; } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 222449085702e5d09eca54796ef1fbfc7bca1566..4239a5d8d0b7607c4b5e7fc7adf291b4d706d5fd 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -16,6 +16,7 @@ package android.app; +import android.util.ArrayMap; import com.android.internal.util.ArrayUtils; import android.content.BroadcastReceiver; @@ -40,7 +41,7 @@ import android.os.Trace; import android.os.UserHandle; import android.util.AndroidRuntimeException; import android.util.Slog; -import android.view.CompatibilityInfoHolder; +import android.view.DisplayAdjustments; import android.view.Display; import java.io.File; @@ -49,8 +50,6 @@ import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.URL; import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; final class IntentReceiverLeaked extends AndroidRuntimeException { public IntentReceiverLeaked(String msg) { @@ -84,19 +83,19 @@ public final class LoadedApk { private final ClassLoader mBaseClassLoader; private final boolean mSecurityViolation; private final boolean mIncludeCode; - public final CompatibilityInfoHolder mCompatibilityInfo = new CompatibilityInfoHolder(); + private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments(); Resources mResources; private ClassLoader mClassLoader; private Application mApplication; - private final HashMap> mReceivers - = new HashMap>(); - private final HashMap> mUnregisteredReceivers - = new HashMap>(); - private final HashMap> mServices - = new HashMap>(); - private final HashMap> mUnboundServices - = new HashMap>(); + private final ArrayMap> mReceivers + = new ArrayMap>(); + private final ArrayMap> mUnregisteredReceivers + = new ArrayMap>(); + private final ArrayMap> mServices + = new ArrayMap>(); + private final ArrayMap> mUnboundServices + = new ArrayMap>(); int mClientCount = 0; @@ -132,17 +131,17 @@ public final class LoadedApk { mBaseClassLoader = baseLoader; mSecurityViolation = securityViolation; mIncludeCode = includeCode; - mCompatibilityInfo.set(compatInfo); + mDisplayAdjustments.setCompatibilityInfo(compatInfo); if (mAppDir == null) { if (ActivityThread.mSystemContext == null) { ActivityThread.mSystemContext = ContextImpl.createSystemContext(mainThread); + ResourcesManager resourcesManager = ResourcesManager.getInstance(); ActivityThread.mSystemContext.getResources().updateConfiguration( - mainThread.getConfiguration(), - mainThread.getDisplayMetricsLocked( - Display.DEFAULT_DISPLAY, compatInfo), - compatInfo); + resourcesManager.getConfiguration(), + resourcesManager.getDisplayMetricsLocked( + Display.DEFAULT_DISPLAY, mDisplayAdjustments), compatInfo); //Slog.i(TAG, "Created system resources " // + mSystemContext.getResources() + ": " // + mSystemContext.getResources().getConfiguration()); @@ -169,7 +168,7 @@ public final class LoadedApk { mIncludeCode = true; mClassLoader = systemContext.getClassLoader(); mResources = systemContext.getResources(); - mCompatibilityInfo.set(compatInfo); + mDisplayAdjustments.setCompatibilityInfo(compatInfo); } public String getPackageName() { @@ -184,6 +183,14 @@ public final class LoadedApk { return mSecurityViolation; } + public CompatibilityInfo getCompatibilityInfo() { + return mDisplayAdjustments.getCompatibilityInfo(); + } + + public void setCompatibilityInfo(CompatibilityInfo compatInfo) { + mDisplayAdjustments.setCompatibilityInfo(compatInfo); + } + /** * Gets the array of shared libraries that are listed as * used by the given package. @@ -532,12 +539,11 @@ public final class LoadedApk { public void removeContextRegistrations(Context context, String who, String what) { final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled(); - HashMap rmap = - mReceivers.remove(context); + ArrayMap rmap = + mReceivers.remove(context); if (rmap != null) { - Iterator it = rmap.values().iterator(); - while (it.hasNext()) { - LoadedApk.ReceiverDispatcher rd = it.next(); + for (int i=0; i smap = + ArrayMap smap = mServices.remove(context); if (smap != null) { - Iterator it = smap.values().iterator(); - while (it.hasNext()) { - LoadedApk.ServiceDispatcher sd = it.next(); + for (int i=0; i map = null; + ArrayMap map = null; if (registered) { map = mReceivers.get(context); if (map != null) { @@ -602,7 +607,7 @@ public final class LoadedApk { instrumentation, registered); if (registered) { if (map == null) { - map = new HashMap(); + map = new ArrayMap(); mReceivers.put(context, map); } map.put(r, rd); @@ -618,7 +623,7 @@ public final class LoadedApk { public IIntentReceiver forgetReceiverDispatcher(Context context, BroadcastReceiver r) { synchronized (mReceivers) { - HashMap map = mReceivers.get(context); + ArrayMap map = mReceivers.get(context); LoadedApk.ReceiverDispatcher rd = null; if (map != null) { rd = map.get(r); @@ -628,10 +633,10 @@ public final class LoadedApk { mReceivers.remove(context); } if (r.getDebugUnregister()) { - HashMap holder + ArrayMap holder = mUnregisteredReceivers.get(context); if (holder == null) { - holder = new HashMap(); + holder = new ArrayMap(); mUnregisteredReceivers.put(context, holder); } RuntimeException ex = new IllegalArgumentException( @@ -644,7 +649,7 @@ public final class LoadedApk { return rd.getIIntentReceiver(); } } - HashMap holder + ArrayMap holder = mUnregisteredReceivers.get(context); if (holder != null) { rd = holder.get(r); @@ -860,14 +865,14 @@ public final class LoadedApk { Context context, Handler handler, int flags) { synchronized (mServices) { LoadedApk.ServiceDispatcher sd = null; - HashMap map = mServices.get(context); + ArrayMap map = mServices.get(context); if (map != null) { sd = map.get(c); } if (sd == null) { sd = new ServiceDispatcher(c, context, handler, flags); if (map == null) { - map = new HashMap(); + map = new ArrayMap(); mServices.put(context, map); } map.put(c, sd); @@ -881,7 +886,7 @@ public final class LoadedApk { public final IServiceConnection forgetServiceDispatcher(Context context, ServiceConnection c) { synchronized (mServices) { - HashMap map + ArrayMap map = mServices.get(context); LoadedApk.ServiceDispatcher sd = null; if (map != null) { @@ -893,10 +898,10 @@ public final class LoadedApk { mServices.remove(context); } if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) { - HashMap holder + ArrayMap holder = mUnboundServices.get(context); if (holder == null) { - holder = new HashMap(); + holder = new ArrayMap(); mUnboundServices.put(context, holder); } RuntimeException ex = new IllegalArgumentException( @@ -908,7 +913,7 @@ public final class LoadedApk { return sd.getIServiceConnection(); } } - HashMap holder + ArrayMap holder = mUnboundServices.get(context); if (holder != null) { sd = holder.get(c); @@ -961,8 +966,8 @@ public final class LoadedApk { } } - private final HashMap mActiveConnections - = new HashMap(); + private final ArrayMap mActiveConnections + = new ArrayMap(); ServiceDispatcher(ServiceConnection conn, Context context, Handler activityThread, int flags) { @@ -992,9 +997,8 @@ public final class LoadedApk { void doForget() { synchronized(this) { - Iterator it = mActiveConnections.values().iterator(); - while (it.hasNext()) { - ServiceDispatcher.ConnectionInfo ci = it.next(); + for (int i=0; i mLoaders = new SparseArray(); + final SparseArray mLoaders = new SparseArray(0); // These are previously run loaders. This list is maintained internally // to avoid destroying a loader while an application is still using it. // It allows an application to restart a loader, but continue using its // previously run loader until the new loader's data is available. - final SparseArray mInactiveLoaders = new SparseArray(); + final SparseArray mInactiveLoaders = new SparseArray(0); final String mWho; diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index 63c6acd4f396f31bd7a4ab5fd1993b1b22f1ae3a..4ca37476db6a2ee90f18b4e5cf0812f86c078324 100644 --- a/core/java/android/app/NativeActivity.java +++ b/core/java/android/app/NativeActivity.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.app; import android.content.Context; @@ -23,17 +24,15 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.Looper; import android.os.MessageQueue; import android.util.AttributeSet; import android.view.InputQueue; -import android.view.KeyEvent; import android.view.Surface; import android.view.SurfaceHolder; import android.view.View; -import android.view.WindowManager; import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import java.io.File; @@ -176,16 +175,20 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null; mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(), - getFilesDir().toString(), getObbDir().toString(), - Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(), - Build.VERSION.SDK_INT, getAssets(), nativeSavedState); - + getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()), + getAbsolutePath(getExternalFilesDir(null)), + Build.VERSION.SDK_INT, getAssets(), nativeSavedState); + if (mNativeHandle == 0) { throw new IllegalArgumentException("Unable to load native library: " + path); } super.onCreate(savedInstanceState); } + private static String getAbsolutePath(File file) { + return (file != null) ? file.getAbsolutePath() : null; + } + @Override protected void onDestroy() { mDestroyed = true; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b66e95bbf1f4dc09d112b60c67b1c2333c1301ea..c63e586a7127c35984facc1e4afe4c729a234427 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -432,58 +432,176 @@ public class Notification implements Parcelable /** * Additional semantic data to be carried around with this Notification. - * @hide + *

+ * The extras keys defined here are intended to capture the original inputs to {@link Builder} + * APIs, and are intended to be used by + * {@link android.service.notification.NotificationListenerService} implementations to extract + * detailed information from notification objects. */ public Bundle extras = new Bundle(); - // extras keys for Builder inputs - /** @hide */ + /** + * {@link #extras} key: this is the title of the notification, + * as supplied to {@link Builder#setContentTitle(CharSequence)}. + */ public static final String EXTRA_TITLE = "android.title"; - /** @hide */ + + /** + * {@link #extras} key: this is the title of the notification when shown in expanded form, + * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. + */ public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; - /** @hide */ + + /** + * {@link #extras} key: this is the main text payload, as supplied to + * {@link Builder#setContentText(CharSequence)}. + */ public static final String EXTRA_TEXT = "android.text"; - /** @hide */ + + /** + * {@link #extras} key: this is a third line of text, as supplied to + * {@link Builder#setSubText(CharSequence)}. + */ public static final String EXTRA_SUB_TEXT = "android.subText"; - /** @hide */ + + /** + * {@link #extras} key: this is a small piece of additional text as supplied to + * {@link Builder#setContentInfo(CharSequence)}. + */ public static final String EXTRA_INFO_TEXT = "android.infoText"; - /** @hide */ + + /** + * {@link #extras} key: this is a line of summary information intended to be shown + * alongside expanded notifications, as supplied to (e.g.) + * {@link BigTextStyle#setSummaryText(CharSequence)}. + */ public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; - /** @hide */ + + /** + * {@link #extras} key: this is the resource ID of the notification's main small icon, as + * supplied to {@link Builder#setSmallIcon(int)}. + */ public static final String EXTRA_SMALL_ICON = "android.icon"; - /** @hide */ + + /** + * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the + * notification payload, as + * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. + */ public static final String EXTRA_LARGE_ICON = "android.largeIcon"; - /** @hide */ + + /** + * {@link #extras} key: this is a bitmap to be used instead of the one from + * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is + * shown in its expanded form, as supplied to + * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. + */ public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; - /** @hide */ + + /** + * {@link #extras} key: this is the progress value supplied to + * {@link Builder#setProgress(int, int, boolean)}. + */ public static final String EXTRA_PROGRESS = "android.progress"; - /** @hide */ + + /** + * {@link #extras} key: this is the maximum value supplied to + * {@link Builder#setProgress(int, int, boolean)}. + */ public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; - /** @hide */ + + /** + * {@link #extras} key: whether the progress bar is indeterminate, supplied to + * {@link Builder#setProgress(int, int, boolean)}. + */ public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; - /** @hide */ + + /** + * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically + * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to + * {@link Builder#setUsesChronometer(boolean)}. + */ public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; - /** @hide */ + + /** + * {@link #extras} key: whether {@link #when} should be shown, + * as supplied to {@link Builder#setShowWhen(boolean)}. + */ public static final String EXTRA_SHOW_WHEN = "android.showWhen"; - /** @hide from BigPictureStyle */ + + /** + * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded + * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. + */ public static final String EXTRA_PICTURE = "android.picture"; - /** @hide from InboxStyle */ + + /** + * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded + * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. + */ public static final String EXTRA_TEXT_LINES = "android.textLines"; - // extras keys for other interesting pieces of information - /** @hide */ + /** + * {@link #extras} key: An array of people that this notification relates to, specified + * by contacts provider contact URI. + */ public static final String EXTRA_PEOPLE = "android.people"; /** - * Structure to encapsulate an "action", including title and icon, that can be attached to a Notification. * @hide + * Extra added by NotificationManagerService to indicate whether a NotificationScorer + * modified the Notifications's score. + */ + public static final String EXTRA_SCORE_MODIFIED = "android.scoreModified"; + + /** + * Not used. + * @hide + */ + public static final String EXTRA_AS_HEADS_UP = "headsup"; + + /** + * Value for {@link #EXTRA_AS_HEADS_UP}. + * @hide + */ + public static final int HEADS_UP_NEVER = 0; + + /** + * Default value for {@link #EXTRA_AS_HEADS_UP}. + * @hide + */ + public static final int HEADS_UP_ALLOWED = 1; + + /** + * Value for {@link #EXTRA_AS_HEADS_UP}. + * @hide + */ + public static final int HEADS_UP_REQUESTED = 2; + + /** + * Structure to encapsulate a named action that can be shown as part of this notification. + * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is + * selected by the user. + *

+ * Apps should use {@link Builder#addAction(int, CharSequence, PendingIntent)} to create and + * attach actions. */ public static class Action implements Parcelable { + /** + * Small icon representing the action. + */ public int icon; + /** + * Title of the action. + */ public CharSequence title; + /** + * Intent to send when the user invokes this action. May be null, in which case the action + * may be rendered in a disabled presentation by the system UI. + */ public PendingIntent actionIntent; - @SuppressWarnings("unused") - public Action() { } + + private Action() { } private Action(Parcel in) { icon = in.readInt(); title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); @@ -491,16 +609,20 @@ public class Notification implements Parcelable actionIntent = PendingIntent.CREATOR.createFromParcel(in); } } - public Action(int icon_, CharSequence title_, PendingIntent intent_) { - this.icon = icon_; - this.title = title_; - this.actionIntent = intent_; + /** + * Use {@link Builder#addAction(int, CharSequence, PendingIntent)}. + */ + public Action(int icon, CharSequence title, PendingIntent intent) { + this.icon = icon; + this.title = title; + this.actionIntent = intent; } + @Override public Action clone() { return new Action( this.icon, - this.title.toString(), + this.title, this.actionIntent // safe to alias ); } @@ -531,7 +653,10 @@ public class Notification implements Parcelable } /** - * @hide + * Array of all {@link Action} structures attached to this notification by + * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of + * {@link android.service.notification.NotificationListenerService} that provide an alternative + * interface for invoking actions. */ public Action[] actions; @@ -1450,7 +1575,6 @@ public class Notification implements Parcelable * called. * * @see Notification#extras - * @hide */ public Builder setExtras(Bundle bag) { mExtras = bag; @@ -1460,8 +1584,15 @@ public class Notification implements Parcelable /** * Add an action to this notification. Actions are typically displayed by * the system as a button adjacent to the notification content. - *
- * A notification displays up to 3 actions, from left to right in the order they were added. + *

+ * Every action must have an icon (32dp square and matching the + * Holo + * Dark action bar visual style), a textual label, and a {@link PendingIntent}. + *

+ * A notification in its expanded form can display up to 3 actions, from left to right in + * the order they were added. Actions will not be displayed when the notification is + * collapsed, however, so be sure that any essential functions may be accessed by the user + * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). * * @param icon Resource ID of a drawable that represents the action. * @param title Text describing the action. @@ -1658,8 +1789,9 @@ public class Notification implements Parcelable /** * Apply the unstyled operations and return a new {@link Notification} object. + * @hide */ - private Notification buildUnstyled() { + public Notification buildUnstyled() { Notification n = new Notification(); n.when = mWhen; n.icon = mSmallIcon; @@ -1737,12 +1869,10 @@ public class Notification implements Parcelable * object. */ public Notification build() { - final Notification n; + Notification n = buildUnstyled(); if (mStyle != null) { - n = mStyle.build(); - } else { - n = buildUnstyled(); + n = mStyle.buildStyled(n); } n.extras = mExtras != null ? new Bundle(mExtras) : new Bundle(); @@ -1852,7 +1982,21 @@ public class Notification implements Parcelable } } - public abstract Notification build(); + /** + * @hide + */ + public abstract Notification buildStyled(Notification wip); + + /** + * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is + * attached to. + * + * @return the fully constructed Notification. + */ + public Notification build() { + checkBuilder(); + return mBuilder.build(); + } } /** @@ -1938,10 +2082,11 @@ public class Notification implements Parcelable extras.putParcelable(EXTRA_PICTURE, mPicture); } + /** + * @hide + */ @Override - public Notification build() { - checkBuilder(); - Notification wip = mBuilder.buildUnstyled(); + public Notification buildStyled(Notification wip) { if (mBigLargeIconSet ) { mBuilder.mLargeIcon = mBigLargeIcon; } @@ -2031,10 +2176,11 @@ public class Notification implements Parcelable return contentView; } + /** + * @hide + */ @Override - public Notification build() { - checkBuilder(); - Notification wip = mBuilder.buildUnstyled(); + public Notification buildStyled(Notification wip) { wip.bigContentView = makeBigContentView(); wip.extras.putCharSequence(EXTRA_TEXT, mBigText); @@ -2142,10 +2288,11 @@ public class Notification implements Parcelable return contentView; } + /** + * @hide + */ @Override - public Notification build() { - checkBuilder(); - Notification wip = mBuilder.buildUnstyled(); + public Notification buildStyled(Notification wip) { wip.bigContentView = makeBigContentView(); return wip; diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index dbafc7830a97832356c7d6eb06690bfe7196cc57..3ee430639ab1a9afae57e96d5239e5ff9fe12828 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -133,7 +133,7 @@ public class NotificationManager } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { - service.enqueueNotificationWithTag(pkg, mContext.getBasePackageName(), tag, id, + service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, UserHandle.myUserId()); if (id != idOut[0]) { Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); @@ -158,7 +158,7 @@ public class NotificationManager } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { - service.enqueueNotificationWithTag(pkg, mContext.getBasePackageName(), tag, id, + service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, user.getIdentifier()); if (id != idOut[0]) { Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java new file mode 100644 index 0000000000000000000000000000000000000000..f55dba42799906800e9af42077ac693978e5920c --- /dev/null +++ b/core/java/android/app/ResourcesManager.java @@ -0,0 +1,293 @@ +/* + * 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.app; + +import static android.app.ActivityThread.DEBUG_CONFIGURATION; + +import android.content.pm.ActivityInfo; +import android.content.res.AssetManager; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ResourcesKey; +import android.hardware.display.DisplayManagerGlobal; +import android.os.IBinder; +import android.util.ArrayMap; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayAdjustments; + +import java.lang.ref.WeakReference; +import java.util.Locale; + +/** @hide */ +public class ResourcesManager { + static final String TAG = "ResourcesManager"; + static final boolean DEBUG_CACHE = false; + static final boolean DEBUG_STATS = true; + + private static ResourcesManager sResourcesManager; + final ArrayMap > mActiveResources + = new ArrayMap >(); + + final ArrayMap mDefaultDisplayMetrics + = new ArrayMap(); + + CompatibilityInfo mResCompatibilityInfo; + + Configuration mResConfiguration; + final Configuration mTmpConfig = new Configuration(); + + public static ResourcesManager getInstance() { + synchronized (ResourcesManager.class) { + if (sResourcesManager == null) { + sResourcesManager = new ResourcesManager(); + } + return sResourcesManager; + } + } + + public Configuration getConfiguration() { + return mResConfiguration; + } + + public void flushDisplayMetricsLocked() { + mDefaultDisplayMetrics.clear(); + } + + public DisplayMetrics getDisplayMetricsLocked(int displayId) { + return getDisplayMetricsLocked(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + } + + public DisplayMetrics getDisplayMetricsLocked(int displayId, DisplayAdjustments daj) { + boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(daj) : null; + if (dm != null) { + return dm; + } + dm = new DisplayMetrics(); + + DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance(); + if (displayManager == null) { + // may be null early in system startup + dm.setToDefaults(); + return dm; + } + + if (isDefaultDisplay) { + mDefaultDisplayMetrics.put(daj, dm); + } + + Display d = displayManager.getCompatibleDisplay(displayId, daj); + if (d != null) { + d.getMetrics(dm); + } else { + // Display no longer exists + // FIXME: This would not be a problem if we kept the Display object around + // instead of using the raw display id everywhere. The Display object caches + // its information even after the display has been removed. + dm.setToDefaults(); + } + //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h=" + // + metrics.heightPixels + " den=" + metrics.density + // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi); + return dm; + } + + final void applyNonDefaultDisplayMetricsToConfigurationLocked( + DisplayMetrics dm, Configuration config) { + config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; + config.densityDpi = dm.densityDpi; + config.screenWidthDp = (int)(dm.widthPixels / dm.density); + config.screenHeightDp = (int)(dm.heightPixels / dm.density); + int sl = Configuration.resetScreenLayout(config.screenLayout); + if (dm.widthPixels > dm.heightPixels) { + config.orientation = Configuration.ORIENTATION_LANDSCAPE; + config.screenLayout = Configuration.reduceScreenLayout(sl, + config.screenWidthDp, config.screenHeightDp); + } else { + config.orientation = Configuration.ORIENTATION_PORTRAIT; + config.screenLayout = Configuration.reduceScreenLayout(sl, + config.screenHeightDp, config.screenWidthDp); + } + config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate + config.compatScreenWidthDp = config.screenWidthDp; + config.compatScreenHeightDp = config.screenHeightDp; + config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; + } + + public boolean applyCompatConfiguration(int displayDensity, + Configuration compatConfiguration) { + if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { + mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); + return true; + } + return false; + } + + /** + * Creates the top level Resources for applications with the given compatibility info. + * + * @param resDir the resource directory. + * @param compatInfo the compability info. Must not be null. + * @param token the application token for determining stack bounds. + */ + public Resources getTopLevelResources(String resDir, int displayId, + Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { + final float scale = compatInfo.applicationScale; + ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, + token); + Resources r; + synchronized (this) { + // Resources is app scale dependent. + if (false) { + Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); + } + WeakReference wr = mActiveResources.get(key); + r = wr != null ? wr.get() : null; + //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); + if (r != null && r.getAssets().isUpToDate()) { + if (false) { + Slog.w(TAG, "Returning cached resources " + r + " " + resDir + + ": appScale=" + r.getCompatibilityInfo().applicationScale); + } + return r; + } + } + + //if (r != null) { + // Slog.w(TAG, "Throwing away out-of-date resources!!!! " + // + r + " " + resDir); + //} + + AssetManager assets = new AssetManager(); + if (assets.addAssetPath(resDir) == 0) { + return null; + } + + //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); + DisplayMetrics dm = getDisplayMetricsLocked(displayId); + Configuration config; + boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + final boolean hasOverrideConfig = key.hasOverrideConfiguration(); + if (!isDefaultDisplay || hasOverrideConfig) { + config = new Configuration(getConfiguration()); + if (!isDefaultDisplay) { + applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); + } + if (hasOverrideConfig) { + config.updateFrom(key.mOverrideConfiguration); + } + } else { + config = getConfiguration(); + } + r = new Resources(assets, dm, config, compatInfo, token); + if (false) { + Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + + r.getConfiguration() + " appScale=" + + r.getCompatibilityInfo().applicationScale); + } + + synchronized (this) { + WeakReference wr = mActiveResources.get(key); + Resources existing = wr != null ? wr.get() : null; + if (existing != null && existing.getAssets().isUpToDate()) { + // Someone else already created the resources while we were + // unlocked; go ahead and use theirs. + r.getAssets().close(); + return existing; + } + + // XXX need to remove entries when weak references go away + mActiveResources.put(key, new WeakReference(r)); + return r; + } + } + + public final boolean applyConfigurationToResourcesLocked(Configuration config, + CompatibilityInfo compat) { + if (mResConfiguration == null) { + mResConfiguration = new Configuration(); + } + if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" + + mResConfiguration.seq + ", newSeq=" + config.seq); + return false; + } + int changes = mResConfiguration.updateFrom(config); + flushDisplayMetricsLocked(); + DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY); + + if (compat != null && (mResCompatibilityInfo == null || + !mResCompatibilityInfo.equals(compat))) { + mResCompatibilityInfo = compat; + changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT + | ActivityInfo.CONFIG_SCREEN_SIZE + | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + } + + // set it for java, this also affects newly created Resources + if (config.locale != null) { + Locale.setDefault(config.locale); + } + + Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); + + ApplicationPackageManager.configurationChanged(); + //Slog.i(TAG, "Configuration changed in " + currentPackageName()); + + Configuration tmpConfig = null; + + for (int i=mActiveResources.size()-1; i>=0; i--) { + ResourcesKey key = mActiveResources.keyAt(i); + Resources r = mActiveResources.valueAt(i).get(); + if (r != null) { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + + r + " config to: " + config); + int displayId = key.mDisplayId; + boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + DisplayMetrics dm = defaultDisplayMetrics; + final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); + if (!isDefaultDisplay || hasOverrideConfiguration) { + if (tmpConfig == null) { + tmpConfig = new Configuration(); + } + tmpConfig.setTo(config); + if (!isDefaultDisplay) { + dm = getDisplayMetricsLocked(displayId); + applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig); + } + if (hasOverrideConfiguration) { + tmpConfig.updateFrom(key.mOverrideConfiguration); + } + r.updateConfiguration(tmpConfig, dm, compat); + } else { + r.updateConfiguration(config, dm, compat); + } + //Slog.i(TAG, "Updated app resources " + v.getKey() + // + " " + r + ": " + r.getConfiguration()); + } else { + //Slog.i(TAG, "Removing old resources " + v.getKey()); + mActiveResources.removeAt(i); + } + } + + return changes != 0; + } + +} diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 7dfc589f2541238057441f5eaf7efe459699bbdd..f9c245ef6dfec3a8f39f817fc8dc6a984b79f747 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -869,7 +869,7 @@ public class SearchManager intent.setComponent(comp); if (inclContext) { IActivityManager am = ActivityManagerNative.getDefault(); - Bundle extras = am.getTopActivityExtras(0); + Bundle extras = am.getAssistContextExtras(0); if (extras != null) { intent.replaceExtras(extras); } diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 829b80ca7bc4487042678c2fb77537e9f791f6dd..7bcf43eb8d038867ffb05f196dd2539cebe874b1 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -63,6 +63,13 @@ public class StatusBarManager { public static final int NAVIGATION_HINT_RECENT_NOP = 1 << 2; public static final int NAVIGATION_HINT_BACK_ALT = 1 << 3; + public static final int WINDOW_STATUS_BAR = 1; + public static final int WINDOW_NAVIGATION_BAR = 2; + + public static final int WINDOW_STATE_SHOWING = 0; + public static final int WINDOW_STATE_HIDING = 1; + public static final int WINDOW_STATE_HIDDEN = 2; + private Context mContext; private IStatusBarService mService; private IBinder mToken = new Binder(); @@ -179,4 +186,12 @@ public class StatusBarManager { throw new RuntimeException(ex); } } + + /** @hide */ + public static String windowStateToString(int state) { + if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING"; + if (state == WINDOW_STATE_HIDDEN) return "WINDOW_STATE_HIDDEN"; + if (state == WINDOW_STATE_SHOWING) return "WINDOW_STATE_SHOWING"; + return "WINDOW_STATE_UNKNOWN"; + } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 3342068a48eb69cdab73585966d2cc72e6848df8..ced72f80a0295a17d2d7bd780d7feb53ea845dc1 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -16,21 +16,27 @@ package android.app; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.os.Binder; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -41,13 +47,14 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.DisplayMetrics; import android.util.Log; -import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.List; /** * Provides access to the system wallpaper. With WallpaperManager, you can @@ -61,6 +68,16 @@ public class WallpaperManager { private float mWallpaperXStep = -1; private float mWallpaperYStep = -1; + /** + * Activity Action: Show settings for choosing wallpaper. Do not use directly to construct + * an intent; instead, use {@link #getCropAndSetWallpaperIntent}. + *

Input: {@link Intent#getData} is the URI of the image to crop and set as wallpaper. + *

Output: RESULT_OK if user decided to crop/set the wallpaper, RESULT_CANCEL otherwise + * Activities that support this intent should specify a MIME filter of "image/*" + */ + public static final String ACTION_CROP_AND_SET_WALLPAPER = + "android.service.wallpaper.CROP_AND_SET_WALLPAPER"; + /** * Launch an activity for the user to pick the current global live * wallpaper. @@ -263,6 +280,7 @@ public class WallpaperManager { synchronized (this) { mWallpaper = null; mDefaultWallpaper = null; + mHandler.removeMessages(MSG_CLEAR_WALLPAPER); } } @@ -355,7 +373,7 @@ public class WallpaperManager { /** * Retrieve the current system wallpaper; if - * no wallpaper is set, the system default wallpaper is returned. + * no wallpaper is set, the system built-in static wallpaper is returned. * This is returned as an * abstract Drawable that you can install in a View to display whatever * wallpaper the user has currently set. @@ -372,6 +390,178 @@ public class WallpaperManager { return null; } + /** + * Returns a drawable for the system built-in static wallpaper . + * + */ + public Drawable getBuiltInDrawable() { + return getBuiltInDrawable(0, 0, false, 0, 0); + } + + /** + * Returns a drawable for the system built-in static wallpaper. Based on the parameters, the + * drawable can be cropped and scaled + * + * @param outWidth The width of the returned drawable + * @param outWidth The height of the returned drawable + * @param scaleToFit If true, scale the wallpaper down rather than just cropping it + * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image; + * 0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned + * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image; + * 0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned + * + */ + public Drawable getBuiltInDrawable(int outWidth, int outHeight, + boolean scaleToFit, float horizontalAlignment, float verticalAlignment) { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + return null; + } + Resources resources = mContext.getResources(); + horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment)); + verticalAlignment = Math.max(0, Math.min(1, verticalAlignment)); + + InputStream is = new BufferedInputStream( + resources.openRawResource(com.android.internal.R.drawable.default_wallpaper)); + + if (is == null) { + Log.e(TAG, "default wallpaper input stream is null"); + return null; + } else { + if (outWidth <= 0 || outHeight <= 0) { + Bitmap fullSize = BitmapFactory.decodeStream(is, null, null); + return new BitmapDrawable(resources, fullSize); + } else { + int inWidth; + int inHeight; + { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(is, null, options); + if (options.outWidth != 0 && options.outHeight != 0) { + inWidth = options.outWidth; + inHeight = options.outHeight; + } else { + Log.e(TAG, "default wallpaper dimensions are 0"); + return null; + } + } + + is = new BufferedInputStream(resources.openRawResource( + com.android.internal.R.drawable.default_wallpaper)); + + RectF cropRectF; + + outWidth = Math.min(inWidth, outWidth); + outHeight = Math.min(inHeight, outHeight); + if (scaleToFit) { + cropRectF = getMaxCropRect(inWidth, inHeight, outWidth, outHeight, + horizontalAlignment, verticalAlignment); + } else { + float left = (inWidth - outWidth) * horizontalAlignment; + float right = left + outWidth; + float top = (inHeight - outHeight) * verticalAlignment; + float bottom = top + outHeight; + cropRectF = new RectF(left, top, right, bottom); + } + Rect roundedTrueCrop = new Rect(); + cropRectF.roundOut(roundedTrueCrop); + + if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { + Log.w(TAG, "crop has bad values for full size image"); + return null; + } + + // See how much we're reducing the size of the image + int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / outWidth, + roundedTrueCrop.height() / outHeight); + + // Attempt to open a region decoder + BitmapRegionDecoder decoder = null; + try { + decoder = BitmapRegionDecoder.newInstance(is, true); + } catch (IOException e) { + Log.w(TAG, "cannot open region decoder for default wallpaper"); + } + + Bitmap crop = null; + if (decoder != null) { + // Do region decoding to get crop bitmap + BitmapFactory.Options options = new BitmapFactory.Options(); + if (scaleDownSampleSize > 1) { + options.inSampleSize = scaleDownSampleSize; + } + crop = decoder.decodeRegion(roundedTrueCrop, options); + decoder.recycle(); + } + + if (crop == null) { + // BitmapRegionDecoder has failed, try to crop in-memory + is = new BufferedInputStream(resources.openRawResource( + com.android.internal.R.drawable.default_wallpaper)); + Bitmap fullSize = null; + if (is != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + if (scaleDownSampleSize > 1) { + options.inSampleSize = scaleDownSampleSize; + } + fullSize = BitmapFactory.decodeStream(is, null, options); + } + if (fullSize != null) { + crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, + roundedTrueCrop.top, roundedTrueCrop.width(), + roundedTrueCrop.height()); + } + } + + if (crop == null) { + Log.w(TAG, "cannot decode default wallpaper"); + return null; + } + + // Scale down if necessary + if (outWidth > 0 && outHeight > 0 && + (crop.getWidth() != outWidth || crop.getHeight() != outHeight)) { + Matrix m = new Matrix(); + RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight()); + RectF returnRect = new RectF(0, 0, outWidth, outHeight); + m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); + Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), + (int) returnRect.height(), Bitmap.Config.ARGB_8888); + if (tmp != null) { + Canvas c = new Canvas(tmp); + Paint p = new Paint(); + p.setFilterBitmap(true); + c.drawBitmap(crop, m, p); + crop = tmp; + } + } + + return new BitmapDrawable(resources, crop); + } + } + } + + private static RectF getMaxCropRect(int inWidth, int inHeight, int outWidth, int outHeight, + float horizontalAlignment, float verticalAlignment) { + RectF cropRect = new RectF(); + // Get a crop rect that will fit this + if (inWidth / (float) inHeight > outWidth / (float) outHeight) { + cropRect.top = 0; + cropRect.bottom = inHeight; + float cropWidth = outWidth * (inHeight / (float) outHeight); + cropRect.left = (inWidth - cropWidth) * horizontalAlignment; + cropRect.right = cropRect.left + cropWidth; + } else { + cropRect.left = 0; + cropRect.right = inWidth; + float cropHeight = outHeight * (inWidth / (float) outWidth); + cropRect.top = (inHeight - cropHeight) * verticalAlignment; + cropRect.bottom = cropRect.top + cropHeight; + } + return cropRect; + } + /** * Retrieve the current system wallpaper; if there is no wallpaper set, * a null pointer is returned. This is returned as an @@ -463,7 +653,58 @@ public class WallpaperManager { return null; } } - + + /** + * Gets an Intent that will launch an activity that crops the given + * image and sets the device's wallpaper. If there is a default HOME activity + * that supports cropping wallpapers, it will be preferred as the default. + * Use this method instead of directly creating a {@link #ACTION_CROP_AND_SET_WALLPAPER} + * intent. + * + * @param imageUri The image URI that will be set in the intent. The must be a content + * URI and its provider must resolve its type to "image/*" + * + * @throws IllegalArgumentException if the URI is not a content URI or its MIME type is + * not "image/*" + */ + public Intent getCropAndSetWallpaperIntent(Uri imageUri) { + if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())) { + throw new IllegalArgumentException("Image URI must be of the " + + ContentResolver.SCHEME_CONTENT + " scheme type"); + } + + final PackageManager packageManager = mContext.getPackageManager(); + Intent cropAndSetWallpaperIntent = + new Intent(ACTION_CROP_AND_SET_WALLPAPER, imageUri); + cropAndSetWallpaperIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + // Find out if the default HOME activity supports CROP_AND_SET_WALLPAPER + Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME); + ResolveInfo resolvedHome = packageManager.resolveActivity(homeIntent, + PackageManager.MATCH_DEFAULT_ONLY); + if (resolvedHome != null) { + cropAndSetWallpaperIntent.setPackage(resolvedHome.activityInfo.packageName); + + List cropAppList = packageManager.queryIntentActivities( + cropAndSetWallpaperIntent, 0); + if (cropAppList.size() > 0) { + return cropAndSetWallpaperIntent; + } + } + + // fallback crop activity + cropAndSetWallpaperIntent.setPackage("com.android.wallpapercropper"); + List cropAppList = packageManager.queryIntentActivities( + cropAndSetWallpaperIntent, 0); + if (cropAppList.size() > 0) { + return cropAndSetWallpaperIntent; + } + // If the URI is not of the right type, or for some reason the system wallpaper + // cropper doesn't exist, return null + throw new IllegalArgumentException("Cannot use passed URI to set wallpaper; " + + "check that the type returned by ContentProvider matches image/*"); + } + /** * Change the current system wallpaper to the bitmap in the given resource. * The resource is opened as a raw data stream and copied into the @@ -475,7 +716,7 @@ public class WallpaperManager { * * @param resid The bitmap to save. * - * @throws IOException If an error occurs reverting to the default + * @throws IOException If an error occurs reverting to the built-in * wallpaper. */ public void setResource(int resid) throws IOException { @@ -514,7 +755,7 @@ public class WallpaperManager { * * @param bitmap The bitmap to save. * - * @throws IOException If an error occurs reverting to the default + * @throws IOException If an error occurs reverting to the built-in * wallpaper. */ public void setBitmap(Bitmap bitmap) throws IOException { @@ -553,7 +794,7 @@ public class WallpaperManager { * * @param data A stream containing the raw data to install as a wallpaper. * - * @throws IOException If an error occurs reverting to the default + * @throws IOException If an error occurs reverting to the built-in * wallpaper. */ public void setStream(InputStream data) throws IOException { @@ -775,14 +1016,14 @@ public class WallpaperManager { } /** - * Remove any currently set wallpaper, reverting to the system's default + * Remove any currently set wallpaper, reverting to the system's built-in * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} * is broadcast. * *

This method requires the caller to hold the permission * {@link android.Manifest.permission#SET_WALLPAPER}. * - * @throws IOException If an error occurs reverting to the default + * @throws IOException If an error occurs reverting to the built-in * wallpaper. */ public void clear() throws IOException { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 17e8dd96800683733f5286023dad67c79f5a1b97..ab825314f013021389c9155988a102e9dcdbdee4 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -32,10 +32,17 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.util.Log; +import com.android.org.conscrypt.TrustedCertificateStore; + +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.List; +import java.util.Set; /** * Public interface for managing policies enforced on a device. Most clients @@ -1327,6 +1334,70 @@ public class DevicePolicyManager { return ENCRYPTION_STATUS_UNSUPPORTED; } + /** + * Installs the given certificate as a User CA. + * + * @return false if the certBuffer cannot be parsed or installation is + * interrupted, otherwise true + * @hide + */ + public boolean installCaCert(byte[] certBuffer) { + if (mService != null) { + try { + return mService.installCaCert(certBuffer); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return false; + } + + /** + * Uninstalls the given certificate from the list of User CAs, if present. + * + * @hide + */ + public void uninstallCaCert(byte[] certBuffer) { + if (mService != null) { + try { + mService.uninstallCaCert(certBuffer); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Returns whether there are any user-installed CA certificates. + * + * @hide + */ + public static boolean hasAnyCaCertsInstalled() { + TrustedCertificateStore certStore = new TrustedCertificateStore(); + Set aliases = certStore.userAliases(); + return aliases != null && !aliases.isEmpty(); + } + + /** + * Returns whether this certificate has been installed as a User CA. + * + * @hide + */ + public boolean hasCaCertInstalled(byte[] certBuffer) { + TrustedCertificateStore certStore = new TrustedCertificateStore(); + String alias; + byte[] pemCert; + try { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) certFactory.generateCertificate( + new ByteArrayInputStream(certBuffer)); + return certStore.getCertificateAlias(cert) != null; + } catch (CertificateException ce) { + Log.w(TAG, "Could not parse certificate", ce); + } + return false; + } + /** * Called by an application that is administering the device to disable all cameras * on the device. After setting this, no applications will be able to access any cameras @@ -1527,9 +1598,26 @@ public class DevicePolicyManager { */ public boolean setDeviceOwner(String packageName) throws IllegalArgumentException, IllegalStateException { + return setDeviceOwner(packageName, null); + } + + /** + * @hide + * Sets the given package as the device owner. The package must already be installed and there + * shouldn't be an existing device owner registered, for this call to succeed. Also, this + * method must be called before the device is provisioned. + * @param packageName the package name of the application to be registered as the device owner. + * @param ownerName the human readable name of the institution that owns this device. + * @return whether the package was successfully registered as the device owner. + * @throws IllegalArgumentException if the package name is null or invalid + * @throws IllegalStateException if a device owner is already registered or the device has + * already been provisioned. + */ + public boolean setDeviceOwner(String packageName, String ownerName) + throws IllegalArgumentException, IllegalStateException { if (mService != null) { try { - return mService.setDeviceOwner(packageName); + return mService.setDeviceOwner(packageName, ownerName); } catch (RemoteException re) { Log.w(TAG, "Failed to set device owner"); } @@ -1581,4 +1669,16 @@ public class DevicePolicyManager { } return null; } + + /** @hide */ + public String getDeviceOwnerName() { + if (mService != null) { + try { + return mService.getDeviceOwnerName(); + } catch (RemoteException re) { + Log.w(TAG, "Failed to get device owner"); + } + } + return null; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b2a65bf37508068ea038b2770b415d93167bfcda..172c47c2a94b5ecb36c0cc301f8dd9c6af73f70a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -98,7 +98,11 @@ interface IDevicePolicyManager { void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); - boolean setDeviceOwner(String packageName); + boolean setDeviceOwner(String packageName, String ownerName); boolean isDeviceOwner(String packageName); String getDeviceOwner(); + String getDeviceOwnerName(); + + boolean installCaCert(in byte[] certBuffer); + void uninstallCaCert(in byte[] certBuffer); } diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 8aae45a0f296b9f8e9867165b7f7e470350bdce9..f104d71cd2d2cd91f6b0e9b7ed5e6d16c3d2c308 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -198,7 +198,6 @@ public class AppWidgetHost { * @return a appWidgetId */ public int allocateAppWidgetId() { - try { if (mPackageName == null) { mPackageName = mContext.getPackageName(); @@ -211,20 +210,17 @@ public class AppWidgetHost { } /** - * Get a appWidgetId for a host in the calling process. + * Get a appWidgetId for a host in the given package. * * @return a appWidgetId * @hide */ - public static int allocateAppWidgetIdForSystem(int hostId, int userId) { + public static int allocateAppWidgetIdForPackage(int hostId, int userId, String packageName) { checkCallerIsSystem(); try { if (sService == null) { bindService(); } - Context systemContext = - (Context) ActivityThread.currentActivityThread().getSystemContext(); - String packageName = systemContext.getPackageName(); return sService.allocateAppWidgetId(packageName, hostId, userId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 6fdf3b47b85f8828bc633b6bd73aef6ee8fb00d4..6f929f261ae64d4ecd634a504e268b21df867827 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -128,9 +128,7 @@ public final class BluetoothA2dp implements BluetoothProfile { try { if (mService == null) { if (VDBG) Log.d(TAG,"Binding service..."); - if (!mContext.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth A2DP Service"); - } + doBind(); } } catch (Exception re) { Log.e(TAG,"",re); @@ -157,9 +155,18 @@ public final class BluetoothA2dp implements BluetoothProfile { } } - if (!context.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth A2DP Service"); + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothA2dp.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent); + return false; } + return true; } /*package*/ void close() { @@ -380,6 +387,66 @@ public final class BluetoothA2dp implements BluetoothProfile { return BluetoothProfile.PRIORITY_OFF; } + /** + * Checks if Avrcp device supports the absolute volume feature. + * + * @return true if device supports absolute volume + * @hide + */ + public boolean isAvrcpAbsoluteVolumeSupported() { + if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); + if (mService != null && isEnabled()) { + try { + return mService.isAvrcpAbsoluteVolumeSupported(); + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Tells remote device to adjust volume. Only if absolute volume is supported. + * + * @param direction 1 to increase volume, or -1 to decrease volume + * @hide + */ + public void adjustAvrcpAbsoluteVolume(int direction) { + if (DBG) Log.d(TAG, "adjustAvrcpAbsoluteVolume"); + if (mService != null && isEnabled()) { + try { + mService.adjustAvrcpAbsoluteVolume(direction); + return; + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in adjustAvrcpAbsoluteVolume()", e); + return; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + } + + /** + * Tells remote device to set an absolute volume. Only if absolute volume is supported + * + * @param volume Absolute volume to be set on AVRCP side + * @hide + */ + public void setAvrcpAbsoluteVolume(int volume) { + if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); + if (mService != null && isEnabled()) { + try { + mService.setAvrcpAbsoluteVolume(volume); + return; + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e); + return; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + } + /** * Check if A2DP profile is streaming music. * @@ -446,7 +513,7 @@ public final class BluetoothA2dp implements BluetoothProfile { } } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothA2dp.Stub.asInterface(service); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 79bb476cb2120d3a67e5b6d7bde5b5b86ce5d2f2..e2bc80aad66d0afea95856c304062827efbcf3e3 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -27,7 +27,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.util.Pair; - import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -36,6 +35,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.HashMap; import java.util.LinkedList; +import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; @@ -50,7 +50,7 @@ import java.util.UUID; * devices, and start a scan for Bluetooth LE devices. * *

To get a {@link BluetoothAdapter} representing the local Bluetooth - * adapter, when running on JELLY_BEAN_MR1 and below, call the + * adapter, when running on JELLY_BEAN_MR1 and below, call the * static {@link #getDefaultAdapter} method; when running on JELLY_BEAN_MR2 and * higher, retrieve it through * {@link android.content.Context#getSystemService} with @@ -433,7 +433,7 @@ public final class BluetoothAdapter { if (address == null || address.length != 6) { throw new IllegalArgumentException("Bluetooth address must have 6 bytes"); } - return new BluetoothDevice(String.format("%02X:%02X:%02X:%02X:%02X:%02X", + return new BluetoothDevice(String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], address[2], address[3], address[4], address[5])); } @@ -598,6 +598,25 @@ public final class BluetoothAdapter { return null; } + /** + * enable or disable Bluetooth HCI snoop log. + * + *

Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission + * + * @return true to indicate configure HCI log successfully, or false on + * immediate error + * @hide + */ + public boolean configHciSnoopLog(boolean enable) { + try { + synchronized(mManagerCallback) { + if (mService != null) return mService.configHciSnoopLog(enable); + } + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + /** * Get the UUIDs supported by the local Bluetooth adapter. * @@ -1173,6 +1192,9 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.HEALTH) { BluetoothHealth health = new BluetoothHealth(context, listener); return true; + } else if (profile == BluetoothProfile.MAP) { + BluetoothMap map = new BluetoothMap(context, listener); + return true; } else { return false; } @@ -1221,6 +1243,10 @@ public final class BluetoothAdapter { BluetoothGattServer gattServer = (BluetoothGattServer)proxy; gattServer.close(); break; + case BluetoothProfile.MAP: + BluetoothMap map = (BluetoothMap)proxy; + map.close(); + break; } } @@ -1684,7 +1710,7 @@ public final class BluetoothAdapter { public void onGetDescriptor(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, - ParcelUuid descUuid) { + int descInstId, ParcelUuid descUuid) { // no op } @@ -1714,14 +1740,14 @@ public final class BluetoothAdapter { public void onDescriptorRead(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, - ParcelUuid descrUuid, byte[] value) { + int descInstId, ParcelUuid descrUuid, byte[] value) { // no op } public void onDescriptorWrite(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, - ParcelUuid descrUuid) { + int descInstId, ParcelUuid descrUuid) { // no op } @@ -1732,6 +1758,10 @@ public final class BluetoothAdapter { public void onReadRemoteRssi(String address, int rssi, int status) { // no op } + + public void onListen(int status) { + // no op + } } } diff --git a/core/java/android/bluetooth/BluetoothAssignedNumbers.java b/core/java/android/bluetooth/BluetoothAssignedNumbers.java index 580e9ff56f42544106e27b1d5d1f1d573dad1004..124bdc118d4f0bade8507567be09969fb0345e65 100644 --- a/core/java/android/bluetooth/BluetoothAssignedNumbers.java +++ b/core/java/android/bluetooth/BluetoothAssignedNumbers.java @@ -512,6 +512,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. */ diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 3ee7142d8d6d129287c7da9f5a5bc113462126f8..5822e468b36b0e9ee59f8713d022f24b3ebd37bb 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -219,7 +219,7 @@ public final class BluetoothDevice implements Parcelable { * {@link #BOND_NONE}, * {@link #BOND_BONDING}, * {@link #BOND_BONDED}. - */ + */ public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE"; /** * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. @@ -228,7 +228,7 @@ public final class BluetoothDevice implements Parcelable { * {@link #BOND_NONE}, * {@link #BOND_BONDING}, * {@link #BOND_BONDED}. - */ + */ public static final String EXTRA_PREVIOUS_BOND_STATE = "android.bluetooth.device.extra.PREVIOUS_BOND_STATE"; /** @@ -253,12 +253,26 @@ public final class BluetoothDevice implements Parcelable { */ public static final int BOND_BONDED = 12; - /** @hide */ + /** + * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} + * intents for unbond reason. + * @hide + */ public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON"; - /** @hide */ + + /** + * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} + * intents to indicate pairing method used. Possible values are: + * {@link #PAIRING_VARIANT_PIN}, + * {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION}, + */ public static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT"; - /** @hide */ + + /** + * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} + * intents as the value of passkey. + */ public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; /** @@ -306,7 +320,11 @@ public final class BluetoothDevice implements Parcelable { public static final String ACTION_NAME_FAILED = "android.bluetooth.device.action.NAME_FAILED"; - /** @hide */ + /** + * Broadcast Action: This intent is used to broadcast PAIRING REQUEST + *

Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} to + * receive. + */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST"; @@ -343,6 +361,9 @@ public final class BluetoothDevice implements Parcelable { /**@hide*/ public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2; + /**@hide*/ + public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3; + /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, * Contains package name to return reply intent to. @@ -443,8 +464,8 @@ public final class BluetoothDevice implements Parcelable { public static final int UNBOND_REASON_REMOVED = 9; /** - * The user will be prompted to enter a pin - * @hide + * The user will be prompted to enter a pin or + * a privileged app will enter a pin for user. */ public static final int PAIRING_VARIANT_PIN = 0; @@ -455,8 +476,8 @@ public final class BluetoothDevice implements Parcelable { public static final int PAIRING_VARIANT_PASSKEY = 1; /** - * The user will be prompted to confirm the passkey displayed on the screen - * @hide + * The user will be prompted to confirm the passkey displayed on the screen or + * a privileged app will confirm the passkey for the user. */ public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; @@ -704,10 +725,9 @@ public final class BluetoothDevice implements Parcelable { * the bonding process completes, and its result. *

Android system services will handle the necessary user interactions * to confirm and complete the bonding process. - *

Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + *

Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * * @return false on immediate error, true if bonding will begin - * @hide */ public boolean createBond() { if (sService == null) { @@ -943,7 +963,13 @@ public final class BluetoothDevice implements Parcelable { return BluetoothDevice.ERROR; } - /** @hide */ + /** + * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} + *

Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @return true pin has been set + * false for error + */ public boolean setPin(byte[] pin) { if (sService == null) { Log.e(TAG, "BT not enabled. Cannot set Remote Device pin"); @@ -965,7 +991,13 @@ public final class BluetoothDevice implements Parcelable { return false; } - /** @hide */ + /** + * Confirm passkey for {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION} pairing. + *

Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @return true confirmation has been sent out + * false for error + */ public boolean setPairingConfirmation(boolean confirm) { if (sService == null) { Log.e(TAG, "BT not enabled. Cannot set pairing confirmation"); diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 0752df8370e23be51c4a5154bd6ac72f93834145..a2bb78c4c158f6790f381528ca4950536a778262 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -167,7 +167,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } synchronized(mStateLock) { @@ -261,7 +261,7 @@ public final class BluetoothGatt implements BluetoothProfile { public void onGetDescriptor(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, - ParcelUuid descUuid) { + int descrInstId, ParcelUuid descUuid) { if (DBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid); if (!address.equals(mDevice.getAddress())) { @@ -276,7 +276,7 @@ public final class BluetoothGatt implements BluetoothProfile { if (characteristic == null) return; characteristic.addDescriptor(new BluetoothGattDescriptor( - characteristic, descUuid.getUuid(), 0)); + characteristic, descUuid.getUuid(), descrInstId, 0)); } /** @@ -294,7 +294,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onServicesDiscovered(BluetoothGatt.this, status); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -341,7 +341,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -387,7 +387,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -418,7 +418,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -429,7 +429,8 @@ public final class BluetoothGatt implements BluetoothProfile { public void onDescriptorRead(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, - ParcelUuid descrUuid, byte[] value) { + int descrInstId, ParcelUuid descrUuid, + byte[] value) { if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid); if (!address.equals(mDevice.getAddress())) { @@ -444,7 +445,7 @@ public final class BluetoothGatt implements BluetoothProfile { if (characteristic == null) return; BluetoothGattDescriptor descriptor = characteristic.getDescriptor( - descrUuid.getUuid()); + descrUuid.getUuid(), descrInstId); if (descriptor == null) return; if (status == 0) descriptor.setValue(value); @@ -456,7 +457,7 @@ public final class BluetoothGatt implements BluetoothProfile { mAuthRetry = true; mService.readDescriptor(mClientIf, address, srvcType, srvcInstId, srvcUuid, charInstId, charUuid, - descrUuid, AUTHENTICATION_MITM); + descrInstId, descrUuid, AUTHENTICATION_MITM); } catch (RemoteException e) { Log.e(TAG,"",e); } @@ -467,7 +468,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -478,7 +479,7 @@ public final class BluetoothGatt implements BluetoothProfile { public void onDescriptorWrite(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, - ParcelUuid descrUuid) { + int descrInstId, ParcelUuid descrUuid) { if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid); if (!address.equals(mDevice.getAddress())) { @@ -493,7 +494,7 @@ public final class BluetoothGatt implements BluetoothProfile { if (characteristic == null) return; BluetoothGattDescriptor descriptor = characteristic.getDescriptor( - descrUuid.getUuid()); + descrUuid.getUuid(), descrInstId); if (descriptor == null) return; if ((status == GATT_INSUFFICIENT_AUTHENTICATION @@ -503,7 +504,7 @@ public final class BluetoothGatt implements BluetoothProfile { mAuthRetry = true; mService.writeDescriptor(mClientIf, address, srvcType, srvcInstId, srvcUuid, charInstId, charUuid, - descrUuid, characteristic.getWriteType(), + descrInstId, descrUuid, characteristic.getWriteType(), AUTHENTICATION_MITM, descriptor.getValue()); } catch (RemoteException e) { Log.e(TAG,"",e); @@ -515,7 +516,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -532,7 +533,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onReliableWriteCompleted(BluetoothGatt.this, status); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -549,9 +550,17 @@ public final class BluetoothGatt implements BluetoothProfile { try { mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } + + /** + * Listen command status callback + * @hide + */ + public void onListen(int status) { + if (DBG) Log.d(TAG, "onListen() - status=" + status); + } }; /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) { @@ -684,6 +693,71 @@ public final class BluetoothGatt implements BluetoothProfile { return true; } + /** + * Starts or stops sending of advertisement packages to listen for connection + * requests from a central devices. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @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; + + try { + mService.clientListen(mClientIf, start); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + + /** + * Sets the advertising data contained in the adv. response packet. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param advData true to set adv. data, false to set scan response + * @param includeName Inlucde the name in the adv. response + * @param includeTxPower Include TX power value + * @param minInterval Minimum desired scan interval (optional) + * @param maxInterval Maximum desired scan interval (optional) + * @param appearance The appearance flags for the device (optional) + * @param manufacturerData Manufacturer specific data including company ID (optional) + */ + /*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; + + byte[] data = new byte[0]; + if (manufacturerData != null) { + data = new byte[manufacturerData.length]; + for(int i = 0; i != manufacturerData.length; ++i) { + data[i] = manufacturerData[i]; + } + } + + try { + mService.setAdvData(mClientIf, !advData, + includeName, includeTxPower, + minInterval != null ? minInterval : 0, + maxInterval != null ? maxInterval : 0, + appearance != null ? appearance : 0, data); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + /** * Disconnects an established connection, or cancels a connection attempt * currently in progress. @@ -915,11 +989,11 @@ public final class BluetoothGatt implements BluetoothProfile { if (device == null) return false; try { - mService.readDescriptor(mClientIf, device.getAddress(), - service.getType(), service.getInstanceId(), - new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), - new ParcelUuid(characteristic.getUuid()), - new ParcelUuid(descriptor.getUuid()), AUTHENTICATION_NONE); + mService.readDescriptor(mClientIf, device.getAddress(), service.getType(), + service.getInstanceId(), new ParcelUuid(service.getUuid()), + characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), + descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()), + AUTHENTICATION_NONE); } catch (RemoteException e) { Log.e(TAG,"",e); return false; @@ -953,11 +1027,10 @@ public final class BluetoothGatt implements BluetoothProfile { if (device == null) return false; try { - mService.writeDescriptor(mClientIf, device.getAddress(), - service.getType(), service.getInstanceId(), - new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), - new ParcelUuid(characteristic.getUuid()), - new ParcelUuid(descriptor.getUuid()), + mService.writeDescriptor(mClientIf, device.getAddress(), service.getType(), + service.getInstanceId(), new ParcelUuid(service.getUuid()), + characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), + descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()), characteristic.getWriteType(), AUTHENTICATION_NONE, descriptor.getValue()); } catch (RemoteException e) { @@ -1037,7 +1110,7 @@ public final class BluetoothGatt implements BluetoothProfile { * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. */ - public void abortReliableWrite(BluetoothDevice mDevice) { + public void abortReliableWrite() { if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return; @@ -1048,6 +1121,13 @@ public final class BluetoothGatt implements BluetoothProfile { } } + /** + * @deprecated Use {@link #abortReliableWrite()} + */ + public void abortReliableWrite(BluetoothDevice mDevice) { + abortReliableWrite(); + } + /** * Enable or disable notifications/indications for a given characteristic. * diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java index 033f079816588d6bdbc4fc948362ecb520399840..f0ecbb4b504704055a37093b2c7c0c244a55d8cc 100644 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -282,6 +282,20 @@ public class BluetoothGattCharacteristic { return true; } + /** + * Get a descriptor by UUID and isntance id. + * @hide + */ + /*package*/ BluetoothGattDescriptor getDescriptor(UUID uuid, int instanceId) { + for(BluetoothGattDescriptor descriptor : mDescriptors) { + if (descriptor.getUuid().equals(uuid) + && descriptor.getInstanceId() == instanceId) { + return descriptor; + } + } + return null; + } + /** * Returns the service this characteristic belongs to. * @return The asscociated service diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java index 1cd68787ce0467e788b638e9b99c361855c94c8d..5f525dc609a0399f58a2e806865953502fae38ef 100644 --- a/core/java/android/bluetooth/BluetoothGattDescriptor.java +++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java @@ -90,6 +90,12 @@ public class BluetoothGattDescriptor { */ protected UUID mUuid; + /** + * Instance ID for this descriptor. + * @hide + */ + protected int mInstance; + /** * Permissions for this descriptor * @hide @@ -116,7 +122,7 @@ public class BluetoothGattDescriptor { * @param permissions Permissions for this descriptor */ public BluetoothGattDescriptor(UUID uuid, int permissions) { - initDescriptor(null, uuid, permissions); + initDescriptor(null, uuid, 0, permissions); } /** @@ -128,14 +134,15 @@ public class BluetoothGattDescriptor { * @param permissions Permissions for this descriptor */ /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid, - int permissions) { - initDescriptor(characteristic, uuid, permissions); + int instance, int permissions) { + initDescriptor(characteristic, uuid, instance, permissions); } private void initDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid, - int permissions) { + int instance, int permissions) { mCharacteristic = characteristic; mUuid = uuid; + mInstance = instance; mPermissions = permissions; } @@ -164,6 +171,21 @@ public class BluetoothGattDescriptor { return mUuid; } + /** + * Returns the instance ID for this descriptor. + * + *

If a remote device offers multiple descriptors with the same UUID, + * the instance ID is used to distuinguish between descriptors. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @return Instance ID of this descriptor + * @hide + */ + public int getInstanceId() { + return mInstance; + } + /** * Returns the permissions for this descriptor. * diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index 78d536bffa1a1dd4f0050d61332267d5307c167a..58ee54fdb1004525a7e8ac63b273707849c97c7b 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -108,7 +108,7 @@ public final class BluetoothGattServer implements BluetoothProfile { connected ? BluetoothProfile.STATE_CONNECTED : BluetoothProfile.STATE_DISCONNECTED); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -128,7 +128,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mCallback.onServiceAdded((int)status, service); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -154,7 +154,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -186,7 +186,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mCallback.onDescriptorReadRequest(device, transId, offset, descriptor); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -214,7 +214,7 @@ public final class BluetoothGattServer implements BluetoothProfile { mCallback.onCharacteristicWriteRequest(device, transId, characteristic, isPrep, needRsp, offset, value); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -250,7 +250,7 @@ public final class BluetoothGattServer implements BluetoothProfile { mCallback.onDescriptorWriteRequest(device, transId, descriptor, isPrep, needRsp, offset, value); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } @@ -270,7 +270,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mCallback.onExecuteWrite(device, transId, execWrite); } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); + Log.w(TAG, "Unhandled exception in callback", ex); } } }; diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java index 39a435becb502c685f729aa6b6081ec6f2aeeafa..1e66369601057e5c75da559cf036e0c604e18d0b 100644 --- a/core/java/android/bluetooth/BluetoothGattService.java +++ b/core/java/android/bluetooth/BluetoothGattService.java @@ -152,8 +152,8 @@ public class BluetoothGattService { */ /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) { for(BluetoothGattCharacteristic characteristic : mCharacteristics) { - if (uuid.equals(characteristic.getUuid()) && - mInstanceId == instanceId) + if (uuid.equals(characteristic.getUuid()) + && characteristic.getInstanceId() == instanceId) return characteristic; } return null; diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 793d79858c6c0ed94b045956b137a9cf9f53e574..8ee955d2379df6d2768cb7cb477def6abcaf5052 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -192,6 +192,11 @@ public final class BluetoothHeadset implements BluetoothProfile { public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = "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 @@ -241,9 +246,7 @@ public final class BluetoothHeadset implements BluetoothProfile { try { if (mService == null) { if (VDBG) Log.d(TAG,"Binding service..."); - if (!mContext.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth Headset Service"); - } + doBind(); } } catch (Exception re) { Log.e(TAG,"",re); @@ -270,9 +273,18 @@ public final class BluetoothHeadset implements BluetoothProfile { } } - if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth Headset Service"); + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothHeadset.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent); + return false; } + return true; } /** @@ -815,17 +827,15 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Notify Headset of phone roam state change. - * This is a backdoor for phone app to call BluetoothHeadset since - * there is currently not a good way to get roaming state change outside - * of phone app. + * Send Headset of CLCC response * * @hide */ - public void roamChanged(boolean roaming) { + public void clccResponse(int index, int direction, int status, int mode, boolean mpty, + String number, int type) { if (mService != null && isEnabled()) { try { - mService.roamChanged(roaming); + mService.clccResponse(index, direction, status, mode, mpty, number, type); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -836,25 +846,46 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Send Headset of CLCC response + * Sends a vendor-specific unsolicited result code to the headset. * - * @hide - */ - public void clccResponse(int index, int direction, int status, int mode, boolean mpty, - String number, int type) { - if (mService != null && isEnabled()) { + *

The actual string to be sent is command + ": " + arg. + * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} + * is {@code "0"}, the string "+ANDROID: 0" will be sent. + * + *

Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. + * + *

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 { - mService.clccResponse(index, direction, status, mode, mpty, number, type); + return mService.sendVendorSpecificResultCode(device, command, arg); } catch (RemoteException e) { - Log.e(TAG, e.toString()); + Log.e(TAG, Log.getStackTraceString(new Throwable())); } - } else { + } + if (mService == null) { Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } + return false; } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothHeadset.Stub.asInterface(service); diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java index cb23662f6c111821f098e6a3c84193970d823861..2e950faeb4e1a0bb4b911006cfd9e6e98beecedb 100644 --- a/core/java/android/bluetooth/BluetoothHealth.java +++ b/core/java/android/bluetooth/BluetoothHealth.java @@ -117,9 +117,7 @@ public final class BluetoothHealth implements BluetoothProfile { try { if (mService == null) { if (VDBG) Log.d(TAG,"Binding service..."); - if (!mContext.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth Health Service"); - } + doBind(); } } catch (Exception re) { Log.e(TAG,"",re); @@ -483,9 +481,18 @@ public final class BluetoothHealth implements BluetoothProfile { } } - if (!context.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth Health Service"); + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothHealth.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent); + return false; } + return true; } /*package*/ void close() { @@ -512,7 +519,7 @@ public final class BluetoothHealth implements BluetoothProfile { mServiceListener = null; } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothHealth.Stub.asInterface(service); diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java index db7e424d40ee2837893838d995eb0da9616c0bd8..844f432ca32fce0c815fff87bd908ea10a0942fb 100644 --- a/core/java/android/bluetooth/BluetoothInputDevice.java +++ b/core/java/android/bluetooth/BluetoothInputDevice.java @@ -206,9 +206,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { try { if (mService == null) { if (VDBG) Log.d(TAG,"Binding service..."); - if (!mContext.bindService(new Intent(IBluetoothInputDevice.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth HID Service"); - } + doBind(); } } catch (Exception re) { Log.e(TAG,"",re); @@ -237,10 +235,18 @@ public final class BluetoothInputDevice implements BluetoothProfile { } } - if (!context.bindService(new Intent(IBluetoothInputDevice.class.getName()), - mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth HID Service"); + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothInputDevice.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent); + return false; } + return true; } /*package*/ void close() { @@ -452,7 +458,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { return BluetoothProfile.PRIORITY_OFF; } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothInputDevice.Stub.asInterface(service); diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java new file mode 100644 index 0000000000000000000000000000000000000000..92a2f1e4253c9a04f0d25a98356a24a804c3ca18 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2008 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.bluetooth; + +import java.util.List; +import java.util.ArrayList; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +/** + * This class provides the APIs to control the Bluetooth MAP + * Profile. + *@hide + */ +public final class BluetoothMap implements BluetoothProfile { + + private static final String TAG = "BluetoothMap"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; + + private IBluetoothMap mService; + private final Context mContext; + private ServiceListener mServiceListener; + private BluetoothAdapter mAdapter; + + /** There was an error trying to obtain the state */ + public static final int STATE_ERROR = -1; + + public static final int RESULT_FAILURE = 0; + public static final int RESULT_SUCCESS = 1; + /** Connection canceled before completion. */ + public static final int RESULT_CANCELED = 2; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (VDBG) Log.d(TAG,"Binding service..."); + doBind(); + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + + /** + * Create a BluetoothMap proxy object. + */ + /*package*/ BluetoothMap(Context context, ServiceListener l) { + if (DBG) Log.d(TAG, "Create BluetoothMap proxy object"); + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothMap.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth MAP Service with " + intent); + return false; + } + return true; + } + + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Close the connection to the backing service. + * Other public functions of BluetoothMap will return default error + * results once close() has been called. Multiple invocations of close() + * are ok. + */ + public synchronized void close() { + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + mServiceListener = null; + } + + /** + * Get the current state of the BluetoothMap service. + * @return One of the STATE_ return codes, or STATE_ERROR if this proxy + * object is currently not connected to the Map service. + */ + public int getState() { + if (VDBG) log("getState()"); + if (mService != null) { + try { + return mService.getState(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return BluetoothMap.STATE_ERROR; + } + + /** + * Get the currently connected remote Bluetooth device (PCE). + * @return The remote Bluetooth device, or null if not in connected or + * connecting state, or if this proxy object is not connected to + * the Map service. + */ + public BluetoothDevice getClient() { + if (VDBG) log("getClient()"); + if (mService != null) { + try { + return mService.getClient(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return null; + } + + /** + * Returns true if the specified Bluetooth device is connected. + * Returns false if not connected, or if this proxy object is not + * currently connected to the Map service. + */ + public boolean isConnected(BluetoothDevice device) { + if (VDBG) log("isConnected(" + device + ")"); + if (mService != null) { + try { + return mService.isConnected(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Initiate connection. Initiation of outgoing connections is not + * supported for MAP server. + */ + public boolean connect(BluetoothDevice device) { + if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); + return false; + } + + /** + * Initiate disconnect. + * + * @param device Remote Bluetooth Device + * @return false on error, + * true otherwise + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Check class bits for possible Map support. + * This is a simple heuristic that tries to guess if a device with the + * given class bits might support Map. It is not accurate for all + * devices. It tries to err on the side of false positives. + * @return True if this device might support Map. + */ + public static boolean doesClassMatchSink(BluetoothClass btClass) { + // TODO optimize the rule + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.COMPUTER_DESKTOP: + case BluetoothClass.Device.COMPUTER_LAPTOP: + case BluetoothClass.Device.COMPUTER_SERVER: + case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: + return true; + default: + return false; + } + } + + /** + * Get the list of connected devices. Currently at most one. + * + * @return list of connected devices + */ + public List getConnectedDevices() { + if (DBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { + try { + return mService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * Get the list of devices matching specified states. Currently at most one. + * + * @return list of matching devices + */ + public List getDevicesMatchingConnectionStates(int[] states) { + if (DBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { + try { + return mService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * Get connection state of device + * + * @return device connection state + */ + public int getConnectionState(BluetoothDevice device) { + if (DBG) log("getConnectionState(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + /** + * Set priority of the profile + * + *

The device should already be paired. + * Priority can be one of {@link #PRIORITY_ON} or + * {@link #PRIORITY_OFF}, + * + * @param device Paired bluetooth device + * @param priority + * @return true if priority is set, false on error + */ + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + if (priority != BluetoothProfile.PRIORITY_OFF && + priority != BluetoothProfile.PRIORITY_ON) { + return false; + } + try { + return mService.setPriority(device, priority); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get the priority of the profile. + * + *

The priority can be any of: + * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, + * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} + * + * @param device Bluetooth device + * @return priority of the device + */ + public int getPriority(BluetoothDevice device) { + if (VDBG) log("getPriority(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getPriority(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return PRIORITY_OFF; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return PRIORITY_OFF; + } + + private final ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) log("Proxy object connected"); + mService = IBluetoothMap.Stub.asInterface(service); + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.MAP, BluetoothMap.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) log("Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.MAP); + } + } + }; + + private static void log(String msg) { + Log.d(TAG, msg); + } + + private boolean isEnabled() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; + log("Bluetooth is Not enabled"); + return false; + } + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + +} diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index e25ec86a8667ba5a3a48bd4941b6a28caf2ae7c3..b7a37f42b3fffa80ded3f9b15f2d9bd892ec8053 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -137,44 +137,60 @@ public final class BluetoothPan implements BluetoothProfile { } catch (RemoteException re) { Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re); } - Log.d(TAG, "BluetoothPan() call bindService"); - if (!context.bindService(new Intent(IBluetoothPan.class.getName()), - mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth HID Service"); + if (VDBG) Log.d(TAG, "BluetoothPan() call bindService"); + doBind(); + if (VDBG) Log.d(TAG, "BluetoothPan(), bindService called"); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothPan.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Pan Service with " + intent); + return false; } - Log.d(TAG, "BluetoothPan(), bindService called"); + return true; } /*package*/ void close() { if (VDBG) log("close()"); - if (mConnection != null) { - mContext.unbindService(mConnection); - mConnection = null; + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mStateChangeCallback); + } catch (RemoteException re) { + Log.w(TAG,"Unable to unregister BluetoothStateChangeCallback",re); + } } - mServiceListener = null; - try { - mAdapter.getBluetoothManager().unregisterStateChangeCallback(mStateChangeCallback); - } catch (RemoteException re) { - Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re); + + synchronized (mConnection) { + if (mPanService != null) { + try { + mPanService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } } + mServiceListener = null; } protected void finalize() { close(); } - private IBluetoothStateChangeCallback mStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { + final private IBluetoothStateChangeCallback mStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { @Override public void onBluetoothStateChange(boolean on) throws RemoteException { //Handle enable request to bind again. if (on) { Log.d(TAG, "onBluetoothStateChange(on) call bindService"); - if (!mContext.bindService(new Intent(IBluetoothPan.class.getName()), - mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth HID Service"); - } - Log.d(TAG, "BluetoothPan(), bindService called"); + doBind(); + if (VDBG) Log.d(TAG, "BluetoothPan(), bindService called"); } else { if (VDBG) Log.d(TAG,"Unbinding service..."); synchronized (mConnection) { @@ -334,7 +350,7 @@ public final class BluetoothPan implements BluetoothProfile { return false; } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "BluetoothPAN Proxy object connected"); mPanService = IBluetoothPan.Stub.asInterface(service); diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index b5280e5337783c9ba345a6067b26f460ca913d52..7f456528a9fb7d7e693ae98b27abd2ec2de357d9 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -129,11 +129,7 @@ public class BluetoothPbap { try { if (mService == null) { if (VDBG) Log.d(TAG,"Binding service..."); - if (!mContext.bindService( - new Intent(IBluetoothPbap.class.getName()), - mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth PBAP Service"); - } + doBind(); } } catch (Exception re) { Log.e(TAG,"",re); @@ -158,9 +154,18 @@ public class BluetoothPbap { Log.e(TAG,"",e); } } - if (!context.bindService(new Intent(IBluetoothPbap.class.getName()), mConnection, 0)) { - Log.e(TAG, "Could not bind to Bluetooth Pbap Service"); + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothPbap.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent); + return false; } + return true; } protected void finalize() throws Throwable { @@ -192,7 +197,6 @@ public class BluetoothPbap { try { mService = null; mContext.unbindService(mConnection); - mConnection = null; } catch (Exception re) { Log.e(TAG,"",re); } @@ -295,7 +299,7 @@ public class BluetoothPbap { } } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) log("Proxy object connected"); mService = IBluetoothPbap.Stub.asInterface(service); diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 43079f44f5ba49e77107ed4904ac739ee15571ce..15740908b0a3de17271b8c6bbc849c2c1c7d343e 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -97,6 +97,12 @@ public interface BluetoothProfile { */ static public final int GATT_SERVER = 8; + /** + * MAP Profile + * @hide + */ + public static final int MAP = 9; + /** * Default priority for devices that we try to auto-connect to and * and allow incoming connections for the profile diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index a19341c07da41ade6c025323991a6a88b63adf60..d10eaea2fba1d89459a61f3a3268e9039c65eb5e 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Locale; import java.util.UUID; import android.net.LocalSocket; import java.nio.ByteOrder; @@ -473,7 +474,7 @@ public final class BluetoothSocket implements Closeable { return mPort; } private String convertAddr(final byte[] addr) { - return String.format("%02X:%02X:%02X:%02X:%02X:%02X", + return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]); } private String waitSocketSignal(InputStream is) throws IOException { diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java index 81c0a6a87eaf2f42463658e4354d467cf699c528..a9b717693356aa0ad8687ba9efea337052dda0b7 100644 --- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java +++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.net.BaseNetworkStateTracker; import android.os.IBinder; import android.os.ServiceManager; import android.os.INetworkManagementService; @@ -54,7 +55,7 @@ import java.util.concurrent.atomic.AtomicReference; * * @hide */ -public class BluetoothTetheringDataTracker implements NetworkStateTracker { +public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker { private static final String NETWORKTYPE = "BLUETOOTH_TETHER"; private static final String TAG = "BluetoothTethering"; private static final boolean DBG = true; @@ -66,18 +67,12 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker { private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false); private final Object mLinkPropertiesLock = new Object(); - private LinkProperties mLinkProperties; - - private LinkCapabilities mLinkCapabilities; - private final Object mNetworkInfoLock = new Object(); - private NetworkInfo mNetworkInfo; private BluetoothPan mBluetoothPan; private static String mRevTetheredIface; /* For sending events to connectivity service handler */ private Handler mCsHandler; - protected Context mContext; private static BluetoothTetheringDataTracker sInstance; private BtdtHandler mBtdtHandler; private AtomicReference mAsyncChannel = new AtomicReference(null); @@ -152,6 +147,11 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker { // not implemented } + @Override + public void captivePortalCheckCompleted(boolean isCaptivePortal) { + // not implemented + } + /** * Re-enable connectivity to a network after a {@link #teardown()}. */ diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 596223518146e3f7cbe9cf9d9f1bd2e14fa4d874..abdf949ebcfd01ff3fbbf07ead153882dff26525 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -56,6 +56,8 @@ public final class BluetoothUuid { ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid Hid = ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb"); + public static final ParcelUuid Hogp = + ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid PANU = ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid NAP = @@ -64,10 +66,17 @@ public final class BluetoothUuid { ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid PBAP_PSE = ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid MAP = + ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid MNS = + ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid MAS = + ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid[] RESERVED_UUIDS = { AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, - ObexObjectPush, PANU, NAP}; + ObexObjectPush, PANU, NAP, MAP, MNS, MAS}; public static boolean isAudioSource(ParcelUuid uuid) { return uuid.equals(AudioSource); @@ -112,6 +121,16 @@ public final class BluetoothUuid { public static boolean isBnep(ParcelUuid uuid) { return uuid.equals(BNEP); } + public static boolean isMap(ParcelUuid uuid) { + return uuid.equals(MAP); + } + public static boolean isMns(ParcelUuid uuid) { + return uuid.equals(MNS); + } + public static boolean isMas(ParcelUuid uuid) { + return uuid.equals(MAS); + } + /** * Returns true if ParcelUuid is present in uuidArray * diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 80806f97d48bc52a7ccb9730988cda1af3b8abb6..07db8cc9c18afc685daa6be7e42a02e727b60b3c 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -80,4 +80,6 @@ interface IBluetooth // For Socket ParcelFileDescriptor connectSocket(in BluetoothDevice device, int type, in ParcelUuid uuid, int port, int flag); ParcelFileDescriptor createSocketChannel(int type, in String serviceName, in ParcelUuid uuid, int port, int flag); + + boolean configHciSnoopLog(boolean enable); } diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 1f1099868e6e9d897107bc49b1c2fd1eacf7a404..26ff9e274c39cb207aa23a16d63821d33d10c62f 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -32,5 +32,8 @@ interface IBluetoothA2dp { int getConnectionState(in BluetoothDevice device); boolean setPriority(in BluetoothDevice device, int priority); int getPriority(in BluetoothDevice device); + boolean isAvrcpAbsoluteVolumeSupported(); + oneway void adjustAvrcpAbsoluteVolume(int direction); + oneway void setAvrcpAbsoluteVolume(int volume); boolean isA2dpPlaying(in BluetoothDevice device); } diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index c89d132df7d3c1b1854b941a9a10f54a47d7ea08..df393dbb2899b372e227cbc9407288be2eb55459 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -37,6 +37,10 @@ interface IBluetoothGatt { void unregisterClient(in int clientIf); void clientConnect(in int clientIf, in String address, in boolean isDirect); void clientDisconnect(in int clientIf, in String address); + void clientListen(in int clientIf, in boolean start); + void setAdvData(in int clientIf, in boolean setScanRsp, in boolean inclName, + in boolean inclTxPower, in int minInterval, in int maxInterval, + in int appearance, in byte[] manufacturerData); void refreshDevice(in int clientIf, in String address); void discoverServices(in int clientIf, in String address); void readCharacteristic(in int clientIf, in String address, in int srvcType, @@ -50,12 +54,13 @@ interface IBluetoothGatt { void readDescriptor(in int clientIf, in String address, in int srvcType, in int srvcInstanceId, in ParcelUuid srvcId, in int charInstanceId, in ParcelUuid charId, - in ParcelUuid descrUuid, in int authReq); + in int descrInstanceId, in ParcelUuid descrUuid, + in int authReq); void writeDescriptor(in int clientIf, in String address, in int srvcType, in int srvcInstanceId, in ParcelUuid srvcId, in int charInstanceId, in ParcelUuid charId, - in ParcelUuid descrId, in int writeType, - in int authReq, in byte[] value); + in int descrInstanceId, in ParcelUuid descrId, + in int writeType, in int authReq, in byte[] value); void registerForNotification(in int clientIf, in String address, in int srvcType, in int srvcInstanceId, in ParcelUuid srvcId, in int charInstanceId, in ParcelUuid charId, diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl index fc521726c6a5dc60485b7da9d78eb4a1816b565e..60c297b59798f1085952a643cb277a7f663d6042 100644 --- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl @@ -39,7 +39,7 @@ interface IBluetoothGattCallback { void onGetDescriptor(in String address, in int srvcType, in int srvcInstId, in ParcelUuid srvcUuid, in int charInstId, in ParcelUuid charUuid, - in ParcelUuid descrUuid); + in int descrInstId, in ParcelUuid descrUuid); void onSearchComplete(in String address, in int status); void onCharacteristicRead(in String address, in int status, in int srvcType, in int srvcInstId, in ParcelUuid srvcUuid, @@ -52,14 +52,16 @@ interface IBluetoothGattCallback { void onDescriptorRead(in String address, in int status, in int srvcType, in int srvcInstId, in ParcelUuid srvcUuid, in int charInstId, in ParcelUuid charUuid, - in ParcelUuid descrUuid, in byte[] value); + in int descrInstId, in ParcelUuid descrUuid, + in byte[] value); void onDescriptorWrite(in String address, in int status, in int srvcType, in int srvcInstId, in ParcelUuid srvcUuid, in int charInstId, in ParcelUuid charUuid, - in ParcelUuid descrUuid); + in int descrInstId, in ParcelUuid descrUuid); void onNotify(in String address, in int srvcType, in int srvcInstId, in ParcelUuid srvcUuid, in int charInstId, in ParcelUuid charUuid, in byte[] value); void onReadRemoteRssi(in String address, in int rssi, in int status); + void onListen(in int status); } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl old mode 100644 new mode 100755 index fc7627ae5c83c9b51b0b6c20dc5fe7889f4ea68a..524ca6f77c7d5fb86d4efb6bee57523582f79b6c --- 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); @@ -50,7 +53,6 @@ interface IBluetoothHeadset { boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device); boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device); void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type); - void roamChanged(boolean roam); void clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type); } diff --git a/core/java/android/bluetooth/IBluetoothMap.aidl b/core/java/android/bluetooth/IBluetoothMap.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d4af63d1fd24fcc64387bb0c361a8baf21e6b28e --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothMap.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2008 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.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * System private API for Bluetooth MAP service + * + * {@hide} + */ +interface IBluetoothMap { + int getState(); + BluetoothDevice getClient(); + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + boolean isConnected(in BluetoothDevice device); + List getConnectedDevices(); + List getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); +} diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java index 898cc4e7a1e20b9683d80ece24eba63183d5b289..809f900222c55d22208c195777a525ff64a24f3d 100644 --- a/core/java/android/content/AbstractThreadedSyncAdapter.java +++ b/core/java/android/content/AbstractThreadedSyncAdapter.java @@ -147,6 +147,7 @@ public abstract class AbstractThreadedSyncAdapter { } private class ISyncAdapterImpl extends ISyncAdapter.Stub { + @Override public void startSync(ISyncContext syncContext, String authority, Account account, Bundle extras) { final SyncContext syncContextClient = new SyncContext(syncContext); @@ -187,6 +188,7 @@ public abstract class AbstractThreadedSyncAdapter { } } + @Override public void cancelSync(ISyncContext syncContext) { // synchronize to make sure that mSyncThreads doesn't change between when we // check it and when we use it diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java index 612c67f01cc05f745ea904ac601a061c82b6ff04..eb7426e4b261e86b41e59d890a73cc9583a989da 100644 --- a/core/java/android/content/AsyncTaskLoader.java +++ b/core/java/android/content/AsyncTaskLoader.java @@ -26,6 +26,7 @@ import android.util.TimeUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; /** * Abstract Loader that provides an {@link AsyncTask} to do the work. See @@ -123,6 +124,8 @@ public abstract class AsyncTaskLoader extends Loader { } } + private final Executor mExecutor; + volatile LoadTask mTask; volatile LoadTask mCancellingTask; @@ -131,7 +134,13 @@ public abstract class AsyncTaskLoader extends Loader { Handler mHandler; public AsyncTaskLoader(Context context) { + this(context, AsyncTask.THREAD_POOL_EXECUTOR); + } + + /** {@hide} */ + public AsyncTaskLoader(Context context, Executor executor) { super(context); + mExecutor = executor; } /** @@ -223,7 +232,7 @@ public abstract class AsyncTaskLoader extends Loader { } } if (DEBUG) Slog.v(TAG, "Executing: " + mTask); - mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + mTask.executeOnExecutor(mExecutor, (Void[]) null); } } diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java index 69f9d4a876b21aef1cf3a5d553bfa296d8d563f0..73e6fd0d8f8c460495a9a7d7a4564c964e9fb881 100644 --- a/core/java/android/content/ClipboardManager.java +++ b/core/java/android/content/ClipboardManager.java @@ -122,7 +122,7 @@ public class ClipboardManager extends android.text.ClipboardManager { if (clip != null) { clip.prepareToLeaveProcess(); } - getService().setPrimaryClip(clip, mContext.getBasePackageName()); + getService().setPrimaryClip(clip, mContext.getOpPackageName()); } catch (RemoteException e) { } } @@ -132,7 +132,7 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public ClipData getPrimaryClip() { try { - return getService().getPrimaryClip(mContext.getBasePackageName()); + return getService().getPrimaryClip(mContext.getOpPackageName()); } catch (RemoteException e) { return null; } @@ -144,7 +144,7 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public ClipDescription getPrimaryClipDescription() { try { - return getService().getPrimaryClipDescription(mContext.getBasePackageName()); + return getService().getPrimaryClipDescription(mContext.getOpPackageName()); } catch (RemoteException e) { return null; } @@ -155,7 +155,7 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public boolean hasPrimaryClip() { try { - return getService().hasPrimaryClip(mContext.getBasePackageName()); + return getService().hasPrimaryClip(mContext.getOpPackageName()); } catch (RemoteException e) { return false; } @@ -166,7 +166,7 @@ public class ClipboardManager extends android.text.ClipboardManager { if (mPrimaryClipChangedListeners.size() == 0) { try { getService().addPrimaryClipChangedListener( - mPrimaryClipChangedServiceListener, mContext.getBasePackageName()); + mPrimaryClipChangedServiceListener, mContext.getOpPackageName()); } catch (RemoteException e) { } } @@ -213,7 +213,7 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public boolean hasText() { try { - return getService().hasClipboardText(mContext.getBasePackageName()); + return getService().hasClipboardText(mContext.getOpPackageName()); } catch (RemoteException e) { return false; } diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java index dad60b0d212113f0dbd44ec357ae34168d50fe15..b96c8d38f99dbe5a6e2cbdf89d167a01270a3b4f 100644 --- a/core/java/android/content/ComponentCallbacks.java +++ b/core/java/android/content/ComponentCallbacks.java @@ -22,6 +22,11 @@ import android.content.res.Configuration; * The set of callback APIs that are common to all application components * ({@link android.app.Activity}, {@link android.app.Service}, * {@link ContentProvider}, and {@link android.app.Application}). + * + *

Note: You should also implement the {@link + * ComponentCallbacks2} interface, which provides the {@link + * ComponentCallbacks2#onTrimMemory} callback to help your app manage its memory usage more + * effectively.

*/ public interface ComponentCallbacks { /** @@ -29,26 +34,35 @@ public interface ComponentCallbacks { * component is running. Note that, unlike activities, other components * are never restarted when a configuration changes: they must always deal * with the results of the change, such as by re-retrieving resources. - * + * *

At the time that this function has been called, your Resources * object will have been updated to return resource values matching the * new configuration. - * + * + *

For more information, read Handling Runtime Changes. + * * @param newConfig The new device configuration. */ void onConfigurationChanged(Configuration newConfig); - + /** * This is called when the overall system is running low on memory, and - * would like actively running process to try to tighten their belt. While + * actively running processes should trim their memory usage. While * the exact point at which this will be called is not defined, generally - * it will happen around the time all background process have been killed, - * that is before reaching the point of killing processes hosting + * it will happen when all background process have been killed. + * That is, before reaching the point of killing processes hosting * service and foreground UI that we would like to avoid killing. - * - *

Applications that want to be nice can implement this method to release - * any caches or other unnecessary resources they may be holding on to. - * The system will perform a gc for you after returning from this method. + * + *

You should implement this method to release + * any caches or other unnecessary resources you may be holding on to. + * The system will perform a garbage collection for you after returning from this method. + *

Preferably, you should implement {@link ComponentCallbacks2#onTrimMemory} from + * {@link ComponentCallbacks2} to incrementally unload your resources based on various + * levels of memory demands. That API is available for API level 14 and higher, so you should + * only use this {@link #onLowMemory} method as a fallback for older versions, which can be + * treated the same as {@link ComponentCallbacks2#onTrimMemory} with the {@link + * ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level.

*/ void onLowMemory(); } diff --git a/core/java/android/content/ComponentCallbacks2.java b/core/java/android/content/ComponentCallbacks2.java index a3b4e5efb5838fa4f42ba13ff3292dbc7ce21fa8..b78548b873b121a15c0876073ed2577d33ba5fbf 100644 --- a/core/java/android/content/ComponentCallbacks2.java +++ b/core/java/android/content/ComponentCallbacks2.java @@ -18,7 +18,68 @@ package android.content; /** * Extended {@link ComponentCallbacks} interface with a new callback for - * finer-grained memory management. + * finer-grained memory management. This interface is available in all application components + * ({@link android.app.Activity}, {@link android.app.Service}, + * {@link ContentProvider}, and {@link android.app.Application}). + * + *

You should implement {@link #onTrimMemory} to incrementally release memory based on current + * system constraints. Using this callback to release your resources helps provide a more + * responsive system overall, but also directly benefits the user experience for + * your app by allowing the system to keep your process alive longer. That is, + * if you don't trim your resources based on memory levels defined by this callback, + * the system is more likely to kill your process while it is cached in the least-recently used + * (LRU) list, thus requiring your app to restart and restore all state when the user returns to it. + * + *

The values provided by {@link #onTrimMemory} do not represent a single linear progression of + * memory limits, but provide you different types of clues about memory availability:

+ *
    + *
  • When your app is running: + *
      + *
    1. {@link #TRIM_MEMORY_RUNNING_MODERATE}
      The device is beginning to run low on memory. + * Your app is running and not killable. + *
    2. {@link #TRIM_MEMORY_RUNNING_LOW}
      The device is running much lower on memory. + * Your app is running and not killable, but please release unused resources to improve system + * performance (which directly impacts your app's performance). + *
    3. {@link #TRIM_MEMORY_RUNNING_CRITICAL}
      The device is running extremely low on memory. + * Your app is not yet considered a killable process, but the system will begin killing + * background processes if apps do not release resources, so you should release non-critical + * resources now to prevent performance degradation. + *
    + *
  • + *
  • When your app's visibility changes: + *
      + *
    1. {@link #TRIM_MEMORY_UI_HIDDEN}
      Your app's UI is no longer visible, so this is a good + * time to release large resources that are used only by your UI. + *
    + *
  • + *
  • When your app's process resides in the background LRU list: + *
      + *
    1. {@link #TRIM_MEMORY_BACKGROUND}
      The system is running low on memory and your process is + * near the beginning of the LRU list. Although your app process is not at a high risk of being + * killed, the system may already be killing processes in the LRU list, so you should release + * resources that are easy to recover so your process will remain in the list and resume + * quickly when the user returns to your app. + *
    2. {@link #TRIM_MEMORY_MODERATE}
      The system is running low on memory and your process is + * near the middle of the LRU list. If the system becomes further constrained for memory, there's a + * chance your process will be killed. + *
    3. {@link #TRIM_MEMORY_COMPLETE}
      The system is running low on memory and your process is + * one of the first to be killed if the system does not recover memory now. You should release + * absolutely everything that's not critical to resuming your app state. + *

      To support API levels lower than 14, you can use the {@link #onLowMemory} method as a + * fallback that's roughly equivalent to the {@link ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level. + *

    4. + *
    + *

    Note: When the system begins + * killing processes in the LRU list, although it primarily works bottom-up, it does give some + * consideration to which processes are consuming more memory and will thus provide more gains in + * memory if killed. So the less memory you consume while in the LRU list overall, the better + * your chances are to remain in the list and be able to quickly resume.

    + *
  • + *
+ *

More information about the different stages of a process lifecycle (such as what it means + * to be placed in the background LRU list) is provided in the Processes and Threads + * document. */ public interface ComponentCallbacks2 extends ComponentCallbacks { diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java index 7ca0f01b986924d939b57061966ec8526de9e608..547a2c3dca102bd1abe9fff48ef47c8d59a0ebc6 100644 --- a/core/java/android/content/ComponentName.java +++ b/core/java/android/content/ComponentName.java @@ -18,6 +18,8 @@ package android.content; import android.os.Parcel; import android.os.Parcelable; + +import java.io.PrintWriter; import java.lang.Comparable; /** @@ -109,6 +111,32 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable PN && className.charAt(PN) == '.') { + sb.append(className, PN, CN); + return; + } + } + sb.append(className); + } + + private static void printShortClassName(PrintWriter pw, String packageName, + String className) { + if (className.startsWith(packageName)) { + int PN = packageName.length(); + int CN = className.length(); + if (CN > PN && className.charAt(PN) == '.') { + pw.write(className, PN, CN-PN); + return; + } + } + pw.print(className); + } + /** * Return a String that unambiguously describes both the package and * class names contained in the ComponentName. You can later recover @@ -137,9 +165,29 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable mCallingPackage = new ThreadLocal(); + private Transport mTransport = new Transport(); /** @@ -196,8 +195,14 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return rejectQuery(uri, projection, selection, selectionArgs, sortOrder, CancellationSignal.fromTransport(cancellationSignal)); } - return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder, - CancellationSignal.fromTransport(cancellationSignal)); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.query( + uri, projection, selection, selectionArgs, sortOrder, + CancellationSignal.fromTransport(cancellationSignal)); + } finally { + setCallingPackage(original); + } } @Override @@ -210,7 +215,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 { if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { return rejectInsert(uri, initialValues); } - return ContentProvider.this.insert(uri, initialValues); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.insert(uri, initialValues); + } finally { + setCallingPackage(original); + } } @Override @@ -218,7 +228,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 { if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { return 0; } - return ContentProvider.this.bulkInsert(uri, initialValues); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.bulkInsert(uri, initialValues); + } finally { + setCallingPackage(original); + } } @Override @@ -240,7 +255,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } } } - return ContentProvider.this.applyBatch(operations); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.applyBatch(operations); + } finally { + setCallingPackage(original); + } } @Override @@ -248,7 +268,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 { if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { return 0; } - return ContentProvider.this.delete(uri, selection, selectionArgs); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.delete(uri, selection, selectionArgs); + } finally { + setCallingPackage(original); + } } @Override @@ -257,26 +282,50 @@ public abstract class ContentProvider implements ComponentCallbacks2 { if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { return 0; } - return ContentProvider.this.update(uri, values, selection, selectionArgs); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.update(uri, values, selection, selectionArgs); + } finally { + setCallingPackage(original); + } } @Override - public ParcelFileDescriptor openFile(String callingPkg, Uri uri, String mode) + public ParcelFileDescriptor openFile( + String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal) throws FileNotFoundException { enforceFilePermission(callingPkg, uri, mode); - return ContentProvider.this.openFile(uri, mode); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.openFile( + uri, mode, CancellationSignal.fromTransport(cancellationSignal)); + } finally { + setCallingPackage(original); + } } @Override - public AssetFileDescriptor openAssetFile(String callingPkg, Uri uri, String mode) + public AssetFileDescriptor openAssetFile( + String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal) throws FileNotFoundException { enforceFilePermission(callingPkg, uri, mode); - return ContentProvider.this.openAssetFile(uri, mode); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.openAssetFile( + uri, mode, CancellationSignal.fromTransport(cancellationSignal)); + } finally { + setCallingPackage(original); + } } @Override public Bundle call(String callingPkg, String method, String arg, Bundle extras) { - return ContentProvider.this.callFromPackage(callingPkg, method, arg, extras); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.call(method, arg, extras); + } finally { + setCallingPackage(original); + } } @Override @@ -286,16 +335,48 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType, - Bundle opts) throws FileNotFoundException { + Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException { enforceFilePermission(callingPkg, uri, "r"); - return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts); + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.openTypedAssetFile( + uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal)); + } finally { + setCallingPackage(original); + } } @Override - public ICancellationSignal createCancellationSignal() throws RemoteException { + public ICancellationSignal createCancellationSignal() { return CancellationSignal.createTransport(); } + @Override + public Uri canonicalize(String callingPkg, Uri uri) { + if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + return null; + } + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.canonicalize(uri); + } finally { + setCallingPackage(original); + } + } + + @Override + public Uri uncanonicalize(String callingPkg, Uri uri) { + if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + return null; + } + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.uncanonicalize(uri); + } finally { + setCallingPackage(original); + } + } + private void enforceFilePermission(String callingPkg, Uri uri, String mode) throws FileNotFoundException, SecurityException { if (mode != null && mode.indexOf('w') != -1) { @@ -457,6 +538,38 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return mContext; } + /** + * Set the calling package, returning the current value (or {@code null}) + * which can be used later to restore the previous state. + */ + private String setCallingPackage(String callingPackage) { + final String original = mCallingPackage.get(); + mCallingPackage.set(callingPackage); + return original; + } + + /** + * Return the package name of the caller that initiated the request being + * processed on the current thread. The returned package will have been + * verified to belong to the calling UID. Returns {@code null} if not + * currently processing a request. + *

+ * This will always return {@code null} when processing + * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests. + * + * @see Binder#getCallingUid() + * @see Context#grantUriPermission(String, Uri, int) + * @throws SecurityException if the calling package doesn't belong to the + * calling UID. + */ + public final String getCallingPackage() { + final String pkg = mCallingPackage.get(); + if (pkg != null) { + mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg); + } + return pkg; + } + /** * Change the permission required to read data from the content * provider. This is normally set for you from its manifest information @@ -526,8 +639,6 @@ public abstract class ContentProvider implements ComponentCallbacks2 { /** @hide */ public final void setAppOps(int readOp, int writeOp) { if (!mNoPerms) { - mTransport.mAppOpsManager = (AppOpsManager)mContext.getSystemService( - Context.APP_OPS_SERVICE); mTransport.mReadOp = readOp; mTransport.mWriteOp = writeOp; } @@ -764,6 +875,58 @@ public abstract class ContentProvider implements ComponentCallbacks2 { */ public abstract String getType(Uri uri); + /** + * Implement this to support canonicalization of URIs that refer to your + * content provider. A canonical URI is one that can be transported across + * devices, backup/restore, and other contexts, and still be able to refer + * to the same data item. Typically this is implemented by adding query + * params to the URI allowing the content provider to verify that an incoming + * canonical URI references the same data as it was originally intended for and, + * if it doesn't, to find that data (if it exists) in the current environment. + * + *

For example, if the content provider holds people and a normal URI in it + * is created with a row index into that people database, the cananical representation + * may have an additional query param at the end which specifies the name of the + * person it is intended for. Later calls into the provider with that URI will look + * up the row of that URI's base index and, if it doesn't match or its entry's + * name doesn't match the name in the query param, perform a query on its database + * to find the correct row to operate on.

+ * + *

If you implement support for canonical URIs, all incoming calls with + * URIs (including this one) must perform this verification and recovery of any + * canonical URIs they receive. In addition, you must also implement + * {@link #uncanonicalize} to strip the canonicalization of any of these URIs.

+ * + *

The default implementation of this method returns null, indicating that + * canonical URIs are not supported.

+ * + * @param url The Uri to canonicalize. + * + * @return Return the canonical representation of url, or null if + * canonicalization of that Uri is not supported. + */ + public Uri canonicalize(Uri url) { + return null; + } + + /** + * Remove canonicalization from canonical URIs previously returned by + * {@link #canonicalize}. For example, if your implementation is to add + * a query param to canonicalize a URI, this method can simply trip any + * query params on the URI. The default implementation always returns the + * same url that was passed in. + * + * @param url The Uri to remove any canonicalization from. + * + * @return Return the non-canonical representation of url, return + * the url as-is if there is nothing to do, or return null if + * the data identified by the canonical representation can not be found in + * the current environment. + */ + public Uri uncanonicalize(Uri url) { + return url; + } + /** * @hide * Implementation when a caller has performed an insert on the content @@ -874,6 +1037,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 { *

The returned ParcelFileDescriptor is owned by the caller, so it is * their responsibility to close it when done. That is, the implementation * of this method should create a new ParcelFileDescriptor for each call. + *

+ * If opened with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor can be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" or "rwt" modes implies a file on disk that + * supports seeking. + *

+ * If you need to detect when the returned ParcelFileDescriptor has been + * closed, or if the remote process has crashed or encountered some other + * error, you can use {@link ParcelFileDescriptor#open(File, int, + * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)}, + * {@link ParcelFileDescriptor#createReliablePipe()}, or + * {@link ParcelFileDescriptor#createReliableSocketPair()}. * *

For use in Intents, you will want to implement {@link #getType} * to return the appropriate MIME type for the data returned here with @@ -903,6 +1078,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @see #openAssetFile(Uri, String) * @see #openFileHelper(Uri, String) * @see #getType(android.net.Uri) + * @see ParcelFileDescriptor#parseMode(String) */ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { @@ -910,6 +1086,75 @@ public abstract class ContentProvider implements ComponentCallbacks2 { + uri); } + /** + * Override this to handle requests to open a file blob. + * The default implementation always throws {@link FileNotFoundException}. + * This method can be called from multiple threads, as described in + * Processes + * and Threads. + * + *

This method returns a ParcelFileDescriptor, which is returned directly + * to the caller. This way large data (such as images and documents) can be + * returned without copying the content. + * + *

The returned ParcelFileDescriptor is owned by the caller, so it is + * their responsibility to close it when done. That is, the implementation + * of this method should create a new ParcelFileDescriptor for each call. + *

+ * If opened with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor can be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" or "rwt" modes implies a file on disk that + * supports seeking. + *

+ * If you need to detect when the returned ParcelFileDescriptor has been + * closed, or if the remote process has crashed or encountered some other + * error, you can use {@link ParcelFileDescriptor#open(File, int, + * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)}, + * {@link ParcelFileDescriptor#createReliablePipe()}, or + * {@link ParcelFileDescriptor#createReliableSocketPair()}. + * + *

For use in Intents, you will want to implement {@link #getType} + * to return the appropriate MIME type for the data returned here with + * the same URI. This will allow intent resolution to automatically determine the data MIME + * type and select the appropriate matching targets as part of its operation.

+ * + *

For better interoperability with other applications, it is recommended + * that for any URIs that can be opened, you also support queries on them + * containing at least the columns specified by {@link android.provider.OpenableColumns}. + * You may also want to support other common columns if you have additional meta-data + * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED} + * in {@link android.provider.MediaStore.MediaColumns}.

+ * + * @param uri The URI whose file is to be opened. + * @param mode Access mode for the file. May be "r" for read-only access, + * "w" for write-only access, "rw" for read and write access, or + * "rwt" for read and write access that truncates any existing + * file. + * @param signal A signal to cancel the operation in progress, or + * {@code null} if none. For example, if you are downloading a + * file from the network to service a "rw" mode request, you + * should periodically call + * {@link CancellationSignal#throwIfCanceled()} to check whether + * the client has canceled the request and abort the download. + * + * @return Returns a new ParcelFileDescriptor which you can use to access + * the file. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the file. + * + * @see #openAssetFile(Uri, String) + * @see #openFileHelper(Uri, String) + * @see #getType(android.net.Uri) + * @see ParcelFileDescriptor#parseMode(String) + */ + public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) + throws FileNotFoundException { + return openFile(uri, mode); + } + /** * This is like {@link #openFile}, but can be implemented by providers * that need to be able to return sub-sections of files, often assets @@ -924,11 +1169,14 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * {@link ContentResolver#openInputStream ContentResolver.openInputStream} * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream} * methods. + *

+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. * *

If you are implementing this to return a full file, you * should create the AssetFileDescriptor with * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with - * applications that can not handle sub-sections of files.

+ * applications that cannot handle sub-sections of files.

* *

For use in Intents, you will want to implement {@link #getType} * to return the appropriate MIME type for the data returned here with @@ -964,6 +1212,68 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; } + /** + * This is like {@link #openFile}, but can be implemented by providers + * that need to be able to return sub-sections of files, often assets + * inside of their .apk. + * This method can be called from multiple threads, as described in + * Processes + * and Threads. + * + *

If you implement this, your clients must be able to deal with such + * file slices, either directly with + * {@link ContentResolver#openAssetFileDescriptor}, or by using the higher-level + * {@link ContentResolver#openInputStream ContentResolver.openInputStream} + * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream} + * methods. + *

+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. + * + *

If you are implementing this to return a full file, you + * should create the AssetFileDescriptor with + * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with + * applications that cannot handle sub-sections of files.

+ * + *

For use in Intents, you will want to implement {@link #getType} + * to return the appropriate MIME type for the data returned here with + * the same URI. This will allow intent resolution to automatically determine the data MIME + * type and select the appropriate matching targets as part of its operation.

+ * + *

For better interoperability with other applications, it is recommended + * that for any URIs that can be opened, you also support queries on them + * containing at least the columns specified by {@link android.provider.OpenableColumns}.

+ * + * @param uri The URI whose file is to be opened. + * @param mode Access mode for the file. May be "r" for read-only access, + * "w" for write-only access (erasing whatever data is currently in + * the file), "wa" for write-only access to append to any existing data, + * "rw" for read and write access on any existing data, and "rwt" for read + * and write access that truncates any existing file. + * @param signal A signal to cancel the operation in progress, or + * {@code null} if none. For example, if you are downloading a + * file from the network to service a "rw" mode request, you + * should periodically call + * {@link CancellationSignal#throwIfCanceled()} to check whether + * the client has canceled the request and abort the download. + * + * @return Returns a new AssetFileDescriptor which you can use to access + * the file. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the file. + * + * @see #openFile(Uri, String) + * @see #openFileHelper(Uri, String) + * @see #getType(android.net.Uri) + */ + public AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal) + throws FileNotFoundException { + return openAssetFile(uri, mode); + } + /** * Convenience for subclasses that wish to implement {@link #openFile} * by looking up a column named "_data" at the given URI. @@ -1002,7 +1312,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { throw new FileNotFoundException("Column _data not found."); } - int modeBits = ContentResolver.modeToMode(uri, mode); + int modeBits = ParcelFileDescriptor.parseMode(mode); return ParcelFileDescriptor.open(new File(path), modeBits); } @@ -1041,6 +1351,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * *

See {@link ClipData} for examples of the use and implementation * of this method. + *

+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. * *

For better interoperability with other applications, it is recommended * that for any URIs that can be opened, you also support queries on them @@ -1086,6 +1399,64 @@ public abstract class ContentProvider implements ComponentCallbacks2 { throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter); } + + /** + * Called by a client to open a read-only stream containing data of a + * particular MIME type. This is like {@link #openAssetFile(Uri, String)}, + * except the file can only be read-only and the content provider may + * perform data conversions to generate data of the desired type. + * + *

The default implementation compares the given mimeType against the + * result of {@link #getType(Uri)} and, if they match, simply calls + * {@link #openAssetFile(Uri, String)}. + * + *

See {@link ClipData} for examples of the use and implementation + * of this method. + *

+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. + * + *

For better interoperability with other applications, it is recommended + * that for any URIs that can be opened, you also support queries on them + * containing at least the columns specified by {@link android.provider.OpenableColumns}. + * You may also want to support other common columns if you have additional meta-data + * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED} + * in {@link android.provider.MediaStore.MediaColumns}.

+ * + * @param uri The data in the content provider being queried. + * @param mimeTypeFilter The type of data the client desires. May be + * a pattern, such as *\/*, if the caller does not have specific type + * requirements; in this case the content provider will pick its best + * type matching the pattern. + * @param opts Additional options from the client. The definitions of + * these are specific to the content provider being called. + * @param signal A signal to cancel the operation in progress, or + * {@code null} if none. For example, if you are downloading a + * file from the network to service a "rw" mode request, you + * should periodically call + * {@link CancellationSignal#throwIfCanceled()} to check whether + * the client has canceled the request and abort the download. + * + * @return Returns a new AssetFileDescriptor from which the client can + * read data of the desired type. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the data. + * @throws IllegalArgumentException Throws IllegalArgumentException if the + * content provider does not support the requested MIME type. + * + * @see #getStreamTypes(Uri, String) + * @see #openAssetFile(Uri, String) + * @see ClipDescription#compareMimeTypes(String, String) + */ + public AssetFileDescriptor openTypedAssetFile( + Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) + throws FileNotFoundException { + return openTypedAssetFile(uri, mimeTypeFilter, opts); + } + /** * Interface to write a stream of data to a pipe. Use with * {@link ContentProvider#openPipeHelper}. @@ -1204,6 +1575,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 { */ if (mContext == null) { mContext = context; + if (context != null) { + mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService( + Context.APP_OPS_SERVICE); + } mMyUid = Process.myUid(); if (info != null) { setReadPermission(info.readPermission); @@ -1242,15 +1617,6 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return results; } - /** - * @hide - * Front-end to {@link #call(String, String, android.os.Bundle)} that provides the name - * of the calling package. - */ - public Bundle callFromPackage(String callingPackag, String method, String arg, Bundle extras) { - return call(method, arg, extras); - } - /** * Call a provider-defined method. This can be used to implement * interfaces that are cheaper and/or unnatural for a table-like diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 8dffac751128a41ad981d7948e556ab7ca6ec350..cefc27f331fdb348cc1679b1ff1c2c46daf044eb 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -16,15 +16,22 @@ package android.content; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.DeadObjectException; +import android.os.Handler; import android.os.ICancellationSignal; -import android.os.RemoteException; +import android.os.Looper; import android.os.ParcelFileDescriptor; -import android.content.res.AssetFileDescriptor; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import dalvik.system.CloseGuard; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -43,58 +50,96 @@ import java.util.ArrayList; * until you are finished with the data they have returned. */ public class ContentProviderClient { - private final IContentProvider mContentProvider; + private static final String TAG = "ContentProviderClient"; + + @GuardedBy("ContentProviderClient.class") + private static Handler sAnrHandler; + private final ContentResolver mContentResolver; + private final IContentProvider mContentProvider; private final String mPackageName; private final boolean mStable; + + private final CloseGuard mGuard = CloseGuard.get(); + + private long mAnrTimeout; + private NotRespondingRunnable mAnrRunnable; + private boolean mReleased; - /** - * @hide - */ - ContentProviderClient(ContentResolver contentResolver, - IContentProvider contentProvider, boolean stable) { - mContentProvider = contentProvider; + /** {@hide} */ + ContentProviderClient( + ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) { mContentResolver = contentResolver; + mContentProvider = contentProvider; mPackageName = contentResolver.mPackageName; mStable = stable; + + mGuard.open("release"); } - /** See {@link ContentProvider#query ContentProvider.query} */ - public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder) throws RemoteException { - try { - return query(url, projection, selection, selectionArgs, sortOrder, null); - } catch (DeadObjectException e) { - if (!mStable) { - mContentResolver.unstableProviderDied(mContentProvider); + /** {@hide} */ + public void setDetectNotResponding(long timeoutMillis) { + synchronized (ContentProviderClient.class) { + mAnrTimeout = timeoutMillis; + + if (timeoutMillis > 0) { + if (mAnrRunnable == null) { + mAnrRunnable = new NotRespondingRunnable(); + } + if (sAnrHandler == null) { + sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */); + } + } else { + mAnrRunnable = null; } - throw e; + } + } + + private void beforeRemote() { + if (mAnrRunnable != null) { + sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout); + } + } + + private void afterRemote() { + if (mAnrRunnable != null) { + sAnrHandler.removeCallbacks(mAnrRunnable); } } /** See {@link ContentProvider#query ContentProvider.query} */ public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) - throws RemoteException { - ICancellationSignal remoteCancellationSignal = null; - if (cancellationSignal != null) { - remoteCancellationSignal = mContentProvider.createCancellationSignal(); - cancellationSignal.setRemote(remoteCancellationSignal); - } + String[] selectionArgs, String sortOrder) throws RemoteException { + return query(url, projection, selection, selectionArgs, sortOrder, null); + } + + /** See {@link ContentProvider#query ContentProvider.query} */ + public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, + String sortOrder, CancellationSignal cancellationSignal) throws RemoteException { + beforeRemote(); try { - return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs, + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = mContentProvider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); + } + return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#getType ContentProvider.getType} */ public String getType(Uri url) throws RemoteException { + beforeRemote(); try { return mContentProvider.getType(url); } catch (DeadObjectException e) { @@ -102,11 +147,14 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { + beforeRemote(); try { return mContentProvider.getStreamTypes(url, mimeTypeFilter); } catch (DeadObjectException e) { @@ -114,12 +162,44 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); + } + } + + /** See {@link ContentProvider#canonicalize} */ + public final Uri canonicalize(Uri url) throws RemoteException { + beforeRemote(); + try { + return mContentProvider.canonicalize(mPackageName, url); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } finally { + afterRemote(); + } + } + + /** See {@link ContentProvider#uncanonicalize} */ + public final Uri uncanonicalize(Uri url) throws RemoteException { + beforeRemote(); + try { + return mContentProvider.uncanonicalize(mPackageName, url); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#insert ContentProvider.insert} */ - public Uri insert(Uri url, ContentValues initialValues) - throws RemoteException { + public Uri insert(Uri url, ContentValues initialValues) throws RemoteException { + beforeRemote(); try { return mContentProvider.insert(mPackageName, url, initialValues); } catch (DeadObjectException e) { @@ -127,11 +207,14 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */ public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException { + beforeRemote(); try { return mContentProvider.bulkInsert(mPackageName, url, initialValues); } catch (DeadObjectException e) { @@ -139,12 +222,15 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#delete ContentProvider.delete} */ public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException { + beforeRemote(); try { return mContentProvider.delete(mPackageName, url, selection, selectionArgs); } catch (DeadObjectException e) { @@ -152,12 +238,15 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#update ContentProvider.update} */ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { + beforeRemote(); try { return mContentProvider.update(mPackageName, url, values, selection, selectionArgs); } catch (DeadObjectException e) { @@ -165,6 +254,8 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } @@ -177,13 +268,34 @@ public class ContentProviderClient { */ public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException { + return openFile(url, mode, null); + } + + /** + * See {@link ContentProvider#openFile ContentProvider.openFile}. Note that + * this does not + * take care of non-content: URIs such as file:. It is strongly recommended + * you use the {@link ContentResolver#openFileDescriptor + * ContentResolver.openFileDescriptor} API instead. + */ + public ParcelFileDescriptor openFile(Uri url, String mode, CancellationSignal signal) + throws RemoteException, FileNotFoundException { + beforeRemote(); try { - return mContentProvider.openFile(mPackageName, url, mode); + ICancellationSignal remoteSignal = null; + if (signal != null) { + signal.throwIfCanceled(); + remoteSignal = mContentProvider.createCancellationSignal(); + signal.setRemote(remoteSignal); + } + return mContentProvider.openFile(mPackageName, url, mode, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } @@ -196,33 +308,71 @@ public class ContentProviderClient { */ public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, FileNotFoundException { + return openAssetFile(url, mode, null); + } + + /** + * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}. + * Note that this does not + * take care of non-content: URIs such as file:. It is strongly recommended + * you use the {@link ContentResolver#openAssetFileDescriptor + * ContentResolver.openAssetFileDescriptor} API instead. + */ + public AssetFileDescriptor openAssetFile(Uri url, String mode, CancellationSignal signal) + throws RemoteException, FileNotFoundException { + beforeRemote(); try { - return mContentProvider.openAssetFile(mPackageName, url, mode); + ICancellationSignal remoteSignal = null; + if (signal != null) { + signal.throwIfCanceled(); + remoteSignal = mContentProvider.createCancellationSignal(); + signal.setRemote(remoteSignal); + } + return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, - String mimeType, Bundle opts) + String mimeType, Bundle opts) throws RemoteException, FileNotFoundException { + return openTypedAssetFileDescriptor(uri, mimeType, opts, null); + } + + /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */ + public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, + String mimeType, Bundle opts, CancellationSignal signal) throws RemoteException, FileNotFoundException { + beforeRemote(); try { - return mContentProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts); + ICancellationSignal remoteSignal = null; + if (signal != null) { + signal.throwIfCanceled(); + remoteSignal = mContentProvider.createCancellationSignal(); + signal.setRemote(remoteSignal); + } + return mContentProvider.openTypedAssetFile( + mPackageName, uri, mimeType, opts, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */ public ContentProviderResult[] applyBatch(ArrayList operations) throws RemoteException, OperationApplicationException { + beforeRemote(); try { return mContentProvider.applyBatch(mPackageName, operations); } catch (DeadObjectException e) { @@ -230,12 +380,14 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } /** See {@link ContentProvider#call(String, String, Bundle)} */ - public Bundle call(String method, String arg, Bundle extras) - throws RemoteException { + public Bundle call(String method, String arg, Bundle extras) throws RemoteException { + beforeRemote(); try { return mContentProvider.call(mPackageName, method, arg, extras); } catch (DeadObjectException e) { @@ -243,6 +395,8 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } @@ -257,6 +411,7 @@ public class ContentProviderClient { throw new IllegalStateException("Already released"); } mReleased = true; + mGuard.close(); if (mStable) { return mContentResolver.releaseProvider(mContentProvider); } else { @@ -265,6 +420,13 @@ public class ContentProviderClient { } } + @Override + protected void finalize() throws Throwable { + if (mGuard != null) { + mGuard.warnIfOpen(); + } + } + /** * Get a reference to the {@link ContentProvider} that is associated with this * client. If the {@link ContentProvider} is running in a different process then @@ -277,4 +439,22 @@ public class ContentProviderClient { public ContentProvider getLocalContentProvider() { return ContentProvider.coerceToLocalContentProvider(mContentProvider); } + + /** {@hide} */ + public static void releaseQuietly(ContentProviderClient client) { + if (client != null) { + try { + client.release(); + } catch (Exception ignored) { + } + } + } + + private class NotRespondingRunnable implements Runnable { + @Override + public void run() { + Log.w(TAG, "Detected provider not responding: " + mContentProvider); + mContentResolver.appNotRespondingViaProvider(mContentProvider); + } + } } diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 6f822c160e07ffcefcb4df94721a16e5d597797b..bcf0b637a2136605173cbc26a64127453b309a7e 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -18,22 +18,20 @@ package android.content; import android.content.res.AssetFileDescriptor; import android.database.BulkCursorDescriptor; -import android.database.BulkCursorNative; import android.database.BulkCursorToCursorAdaptor; import android.database.Cursor; import android.database.CursorToBulkCursorAdaptor; import android.database.DatabaseUtils; -import android.database.IBulkCursor; import android.database.IContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Bundle; -import android.os.RemoteException; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.RemoteException; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -227,9 +225,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr String callingPkg = data.readString(); Uri url = Uri.CREATOR.createFromParcel(data); String mode = data.readString(); + ICancellationSignal signal = ICancellationSignal.Stub.asInterface( + data.readStrongBinder()); ParcelFileDescriptor fd; - fd = openFile(callingPkg, url, mode); + fd = openFile(callingPkg, url, mode, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -247,9 +247,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr String callingPkg = data.readString(); Uri url = Uri.CREATOR.createFromParcel(data); String mode = data.readString(); + ICancellationSignal signal = ICancellationSignal.Stub.asInterface( + data.readStrongBinder()); AssetFileDescriptor fd; - fd = openAssetFile(callingPkg, url, mode); + fd = openAssetFile(callingPkg, url, mode, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -296,9 +298,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr Uri url = Uri.CREATOR.createFromParcel(data); String mimeType = data.readString(); Bundle opts = data.readBundle(); + ICancellationSignal signal = ICancellationSignal.Stub.asInterface( + data.readStrongBinder()); AssetFileDescriptor fd; - fd = openTypedAssetFile(callingPkg, url, mimeType, opts); + fd = openTypedAssetFile(callingPkg, url, mimeType, opts, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -319,6 +323,30 @@ abstract public class ContentProviderNative extends Binder implements IContentPr reply.writeStrongBinder(cancellationSignal.asBinder()); return true; } + + case CANONICALIZE_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + String callingPkg = data.readString(); + Uri url = Uri.CREATOR.createFromParcel(data); + + Uri out = canonicalize(callingPkg, url); + reply.writeNoException(); + Uri.writeToParcel(reply, out); + return true; + } + + case UNCANONICALIZE_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + String callingPkg = data.readString(); + Uri url = Uri.CREATOR.createFromParcel(data); + + Uri out = uncanonicalize(callingPkg, url); + reply.writeNoException(); + Uri.writeToParcel(reply, out); + return true; + } } } catch (Exception e) { DatabaseUtils.writeExceptionToParcel(reply, e); @@ -538,7 +566,9 @@ final class ContentProviderProxy implements IContentProvider } } - public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode) + @Override + public ParcelFileDescriptor openFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -548,6 +578,7 @@ final class ContentProviderProxy implements IContentProvider data.writeString(callingPkg); url.writeToParcel(data, 0); data.writeString(mode); + data.writeStrongBinder(signal != null ? signal.asBinder() : null); mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0); @@ -561,7 +592,9 @@ final class ContentProviderProxy implements IContentProvider } } - public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode) + @Override + public AssetFileDescriptor openAssetFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -571,6 +604,7 @@ final class ContentProviderProxy implements IContentProvider data.writeString(callingPkg); url.writeToParcel(data, 0); data.writeString(mode); + data.writeStrongBinder(signal != null ? signal.asBinder() : null); mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0); @@ -629,8 +663,9 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType, - Bundle opts) throws RemoteException, FileNotFoundException { + Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -640,6 +675,7 @@ final class ContentProviderProxy implements IContentProvider url.writeToParcel(data, 0); data.writeString(mimeType); data.writeBundle(opts); + data.writeStrongBinder(signal != null ? signal.asBinder() : null); mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0); @@ -673,5 +709,46 @@ final class ContentProviderProxy implements IContentProvider } } + public Uri canonicalize(String callingPkg, Uri url) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + + data.writeString(callingPkg); + url.writeToParcel(data, 0); + + mRemote.transact(IContentProvider.CANONICALIZE_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + Uri out = Uri.CREATOR.createFromParcel(reply); + return out; + } finally { + data.recycle(); + reply.recycle(); + } + } + + public Uri uncanonicalize(String callingPkg, Uri url) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + + data.writeString(callingPkg); + url.writeToParcel(data, 0); + + mRemote.transact(IContentProvider.UNCANONICALIZE_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + Uri out = Uri.CREATOR.createFromParcel(reply); + return out; + } finally { + data.recycle(); + reply.recycle(); + } + } + private IBinder mRemote; } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index fefd343691549e9d43dfb28c72925b5221df8348..4e6cc925ee5d5fbffc7ca0618a75241507eb3142 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -16,8 +16,6 @@ package android.content; -import dalvik.system.CloseGuard; - import android.accounts.Account; import android.app.ActivityManagerNative; import android.app.ActivityThread; @@ -45,6 +43,8 @@ import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import dalvik.system.CloseGuard; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -55,7 +55,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; - /** * This class provides applications access to the content model. * @@ -72,7 +71,13 @@ public abstract class ContentResolver { */ @Deprecated public static final String SYNC_EXTRAS_ACCOUNT = "account"; + + /** + * If this extra is set to true, the sync request will be scheduled + * at the front of the sync request queue and without any delay + */ public static final String SYNC_EXTRAS_EXPEDITED = "expedited"; + /** * @deprecated instead use * {@link #SYNC_EXTRAS_MANUAL} @@ -104,10 +109,40 @@ public abstract class ContentResolver { */ public static final String SYNC_EXTRAS_MANUAL = "force"; + /** + * Indicates that this sync is intended to only upload local changes to the server. + * For example, this will be set to true if the sync is initiated by a call to + * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)} + */ public static final String SYNC_EXTRAS_UPLOAD = "upload"; + + /** + * Indicates that the sync adapter should proceed with the delete operations, + * even if it determines that there are too many. + * See {@link SyncResult#tooManyDeletions} + */ public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override"; + + /** + * Indicates that the sync adapter should not proceed with the delete operations, + * if it determines that there are too many. + * See {@link SyncResult#tooManyDeletions} + */ public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions"; + /* Extensions to API. TODO: Not clear if we will keep these as public flags. */ + /** {@hide} User-specified flag for expected upload size. */ + public static final String SYNC_EXTRAS_EXPECTED_UPLOAD = "expected_upload"; + + /** {@hide} User-specified flag for expected download size. */ + public static final String SYNC_EXTRAS_EXPECTED_DOWNLOAD = "expected_download"; + + /** {@hide} Priority of this sync with respect to other syncs scheduled for this application. */ + 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_DISALLOW_METERED = "disallow_metered"; + /** * Set by the SyncManager to request that the SyncAdapter initialize itself for * the given account/authority pair. One required initialization step is to @@ -220,22 +255,29 @@ public abstract class ContentResolver { // Always log queries which take 500ms+; shorter queries are // sampled accordingly. + private static final boolean ENABLE_CONTENT_SAMPLE = false; private static final int SLOW_THRESHOLD_MILLIS = 500; private final Random mRandom = new Random(); // guarded by itself public ContentResolver(Context context) { mContext = context != null ? context : ActivityThread.currentApplication(); - mPackageName = mContext.getBasePackageName(); + mPackageName = mContext.getOpPackageName(); } /** @hide */ protected abstract IContentProvider acquireProvider(Context c, String name); - /** Providing a default implementation of this, to avoid having to change - * a lot of other things, but implementations of ContentResolver should - * implement it. @hide */ + + /** + * Providing a default implementation of this, to avoid having to change a + * lot of other things, but implementations of ContentResolver should + * implement it. + * + * @hide + */ protected IContentProvider acquireExistingProvider(Context c, String name) { return acquireProvider(c, name); } + /** @hide */ public abstract boolean releaseProvider(IContentProvider icp); /** @hide */ @@ -245,6 +287,11 @@ public abstract class ContentResolver { /** @hide */ public abstract void unstableProviderDied(IContentProvider icp); + /** @hide */ + public void appNotRespondingViaProvider(IContentProvider icp) { + throw new UnsupportedOperationException("appNotRespondingViaProvider"); + } + /** * Return the MIME type of the given content URL. * @@ -291,7 +338,7 @@ public abstract class ContentResolver { * content URL can be returned when opened as as stream with * {@link #openTypedAssetFileDescriptor}. Note that the types here are * not necessarily a superset of the type returned by {@link #getType} -- - * many content providers can not return a raw stream for the structured + * many content providers cannot return a raw stream for the structured * data that they contain. * * @param url A Uri identifying content (either a list or specific type), @@ -448,6 +495,9 @@ public abstract class ContentResolver { if (qCursor != null) { qCursor.close(); } + if (cancellationSignal != null) { + cancellationSignal.setRemote(null); + } if (unstableProvider != null) { releaseUnstableProvider(unstableProvider); } @@ -457,6 +507,88 @@ public abstract class ContentResolver { } } + /** + * Transform the given url to a canonical representation of + * its referenced resource, which can be used across devices, persisted, + * backed up and restored, etc. The returned Uri is still a fully capable + * Uri for use with its content provider, allowing you to do all of the + * same content provider operations as with the original Uri -- + * {@link #query}, {@link #openInputStream(android.net.Uri)}, etc. The + * only difference in behavior between the original and new Uris is that + * the content provider may need to do some additional work at each call + * using it to resolve it to the correct resource, especially if the + * canonical Uri has been moved to a different environment. + * + *

If you are moving a canonical Uri between environments, you should + * perform another call to {@link #canonicalize} with that original Uri to + * re-canonicalize it for the current environment. Alternatively, you may + * want to use {@link #uncanonicalize} to transform it to a non-canonical + * Uri that works only in the current environment but potentially more + * efficiently than the canonical representation.

+ * + * @param url The {@link Uri} that is to be transformed to a canonical + * representation. Like all resolver calls, the input can be either + * a non-canonical or canonical Uri. + * + * @return Returns the official canonical representation of url, + * or null if the content provider does not support a canonical representation + * of the given Uri. Many providers may not support canonicalization of some + * or all of their Uris. + * + * @see #uncanonicalize + */ + public final Uri canonicalize(Uri url) { + IContentProvider provider = acquireProvider(url); + if (provider == null) { + return null; + } + + try { + return provider.canonicalize(mPackageName, url); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + releaseProvider(provider); + } + } + + /** + * Given a canonical Uri previously generated by {@link #canonicalize}, convert + * it to its local non-canonical form. This can be useful in some cases where + * you know that you will only be using the Uri in the current environment and + * want to avoid any possible overhead when using it with the content + * provider or want to verify that the referenced data exists at all in the + * new environment. + * + * @param url The canonical {@link Uri} that is to be convered back to its + * non-canonical form. + * + * @return Returns the non-canonical representation of url. This will + * return null if data identified by the canonical Uri can not be found in + * the current environment; callers must always check for null and deal with + * that by appropriately falling back to an alternative. + * + * @see #canonicalize + */ + public final Uri uncanonicalize(Uri url) { + IContentProvider provider = acquireProvider(url); + if (provider == null) { + return null; + } + + try { + return provider.uncanonicalize(mPackageName, url); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + releaseProvider(provider); + } + } + /** * Open a stream on to the content associated with a content URI. If there * is no data associated with the URI, FileNotFoundException is thrown. @@ -494,7 +626,7 @@ public abstract class ContentResolver { // with sufficient testing. return new FileInputStream(uri.getPath()); } else { - AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r"); + AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r", null); try { return fd != null ? fd.createInputStream() : null; } catch (IOException e) { @@ -534,7 +666,7 @@ public abstract class ContentResolver { */ public final OutputStream openOutputStream(Uri uri, String mode) throws FileNotFoundException { - AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode); + AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode, null); try { return fd != null ? fd.createOutputStream() : null; } catch (IOException e) { @@ -560,6 +692,15 @@ public abstract class ContentResolver { * *

See {@link #openAssetFileDescriptor(Uri, String)} for more information * on these schemes. + *

+ * If opening with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor could be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" mode implies a file on disk that supports + * seeking. If possible, always use an exclusive mode to give the underlying + * {@link ContentProvider} the most flexibility. + *

+ * If you are writing a file, and need to communicate an error to the + * provider, use {@link ParcelFileDescriptor#closeWithError(String)}. * * @param uri The desired URI to open. * @param mode The file mode to use, as per {@link ContentProvider#openFile @@ -570,9 +711,54 @@ public abstract class ContentResolver { * file exists under the URI or the mode is invalid. * @see #openAssetFileDescriptor(Uri, String) */ + public final ParcelFileDescriptor openFileDescriptor(Uri uri, String mode) + throws FileNotFoundException { + return openFileDescriptor(uri, mode, null); + } + + /** + * Open a raw file descriptor to access data under a URI. This + * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the + * underlying {@link ContentProvider#openFile} + * ContentProvider.openFile()} method, so will not work with + * providers that return sub-sections of files. If at all possible, + * you should use {@link #openAssetFileDescriptor(Uri, String)}. You + * will receive a FileNotFoundException exception if the provider returns a + * sub-section of a file. + * + *

Accepts the following URI schemes:
+ *
    + *
  • content ({@link #SCHEME_CONTENT})
  • + *
  • file ({@link #SCHEME_FILE})
  • + *
+ * + *

See {@link #openAssetFileDescriptor(Uri, String)} for more information + * on these schemes. + *

+ * If opening with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor could be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" mode implies a file on disk that supports + * seeking. If possible, always use an exclusive mode to give the underlying + * {@link ContentProvider} the most flexibility. + *

+ * If you are writing a file, and need to communicate an error to the + * provider, use {@link ParcelFileDescriptor#closeWithError(String)}. + * + * @param uri The desired URI to open. + * @param mode The file mode to use, as per {@link ContentProvider#openFile + * ContentProvider.openFile}. + * @param cancellationSignal A signal to cancel the operation in progress, + * or null if none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. + * @return Returns a new ParcelFileDescriptor pointing to the file. You + * own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException if no + * file exists under the URI or the mode is invalid. + * @see #openAssetFileDescriptor(Uri, String) + */ public final ParcelFileDescriptor openFileDescriptor(Uri uri, - String mode) throws FileNotFoundException { - AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode); + String mode, CancellationSignal cancellationSignal) throws FileNotFoundException { + AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode, cancellationSignal); if (afd == null) { return null; } @@ -640,8 +826,64 @@ public abstract class ContentResolver { * @throws FileNotFoundException Throws FileNotFoundException of no * file exists under the URI or the mode is invalid. */ + public final AssetFileDescriptor openAssetFileDescriptor(Uri uri, String mode) + throws FileNotFoundException { + return openAssetFileDescriptor(uri, mode, null); + } + + /** + * Open a raw file descriptor to access data under a URI. This + * interacts with the underlying {@link ContentProvider#openAssetFile} + * method of the provider associated with the given URI, to retrieve any file stored there. + * + *

Accepts the following URI schemes:
+ *
    + *
  • content ({@link #SCHEME_CONTENT})
  • + *
  • android.resource ({@link #SCHEME_ANDROID_RESOURCE})
  • + *
  • file ({@link #SCHEME_FILE})
  • + *
+ *
The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme
+ *

+ * A Uri object can be used to reference a resource in an APK file. The + * Uri should be one of the following formats: + *

    + *
  • android.resource://package_name/id_number
    + * package_name is your package name as listed in your AndroidManifest.xml. + * For example com.example.myapp
    + * id_number is the int form of the ID.
    + * The easiest way to construct this form is + *
    Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");
    + *
  • + *
  • android.resource://package_name/type/name
    + * package_name is your package name as listed in your AndroidManifest.xml. + * For example com.example.myapp
    + * type is the string form of the resource type. For example, raw + * or drawable. + * name is the string form of the resource name. That is, whatever the file + * name was in your res directory, without the type extension. + * The easiest way to construct this form is + *
    Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");
    + *
  • + *
+ * + *

Note that if this function is called for read-only input (mode is "r") + * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor} + * for you with a MIME type of "*\/*". This allows such callers to benefit + * from any built-in data conversion that a provider implements. + * + * @param uri The desired URI to open. + * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile + * ContentProvider.openAssetFile}. + * @param cancellationSignal A signal to cancel the operation in progress, or null if + * none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. + * @return Returns a new ParcelFileDescriptor pointing to the file. You + * own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * file exists under the URI or the mode is invalid. + */ public final AssetFileDescriptor openAssetFileDescriptor(Uri uri, - String mode) throws FileNotFoundException { + String mode, CancellationSignal cancellationSignal) throws FileNotFoundException { String scheme = uri.getScheme(); if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { if (!"r".equals(mode)) { @@ -655,11 +897,11 @@ public abstract class ContentResolver { } } else if (SCHEME_FILE.equals(scheme)) { ParcelFileDescriptor pfd = ParcelFileDescriptor.open( - new File(uri.getPath()), modeToMode(uri, mode)); + new File(uri.getPath()), ParcelFileDescriptor.parseMode(mode)); return new AssetFileDescriptor(pfd, 0, -1); } else { if ("r".equals(mode)) { - return openTypedAssetFileDescriptor(uri, "*/*", null); + return openTypedAssetFileDescriptor(uri, "*/*", null, cancellationSignal); } else { IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { @@ -669,8 +911,16 @@ public abstract class ContentResolver { AssetFileDescriptor fd = null; try { + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = unstableProvider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); + } + try { - fd = unstableProvider.openAssetFile(mPackageName, uri, mode); + fd = unstableProvider.openAssetFile( + mPackageName, uri, mode, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -684,7 +934,8 @@ public abstract class ContentResolver { if (stableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); } - fd = stableProvider.openAssetFile(mPackageName, uri, mode); + fd = stableProvider.openAssetFile( + mPackageName, uri, mode, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -712,6 +963,9 @@ public abstract class ContentResolver { } catch (FileNotFoundException e) { throw e; } finally { + if (cancellationSignal != null) { + cancellationSignal.setRemote(null); + } if (stableProvider != null) { releaseProvider(stableProvider); } @@ -751,8 +1005,45 @@ public abstract class ContentResolver { * @throws FileNotFoundException Throws FileNotFoundException of no * data of the desired type exists under the URI. */ + public final AssetFileDescriptor openTypedAssetFileDescriptor( + Uri uri, String mimeType, Bundle opts) throws FileNotFoundException { + return openTypedAssetFileDescriptor(uri, mimeType, opts, null); + } + + /** + * Open a raw file descriptor to access (potentially type transformed) + * data from a "content:" URI. This interacts with the underlying + * {@link ContentProvider#openTypedAssetFile} method of the provider + * associated with the given URI, to retrieve retrieve any appropriate + * data stream for the data stored there. + * + *

Unlike {@link #openAssetFileDescriptor}, this function only works + * with "content:" URIs, because content providers are the only facility + * with an associated MIME type to ensure that the returned data stream + * is of the desired type. + * + *

All text/* streams are encoded in UTF-8. + * + * @param uri The desired URI to open. + * @param mimeType The desired MIME type of the returned data. This can + * be a pattern such as *\/*, which will allow the content provider to + * select a type, though there is no way for you to determine what type + * it is returning. + * @param opts Additional provider-dependent options. + * @param cancellationSignal A signal to cancel the operation in progress, + * or null if none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. + * @return Returns a new ParcelFileDescriptor from which you can read the + * data stream from the provider. Note that this may be a pipe, meaning + * you can't seek in it. The only seek you should do is if the + * AssetFileDescriptor contains an offset, to move to that offset before + * reading. You own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * data of the desired type exists under the URI. + */ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, - String mimeType, Bundle opts) throws FileNotFoundException { + String mimeType, Bundle opts, CancellationSignal cancellationSignal) + throws FileNotFoundException { IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); @@ -761,8 +1052,16 @@ public abstract class ContentResolver { AssetFileDescriptor fd = null; try { + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = unstableProvider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); + } + try { - fd = unstableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts); + fd = unstableProvider.openTypedAssetFile( + mPackageName, uri, mimeType, opts, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -776,7 +1075,8 @@ public abstract class ContentResolver { if (stableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); } - fd = stableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts); + fd = stableProvider.openTypedAssetFile( + mPackageName, uri, mimeType, opts, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -804,6 +1104,9 @@ public abstract class ContentResolver { } catch (FileNotFoundException e) { throw e; } finally { + if (cancellationSignal != null) { + cancellationSignal.setRemote(null); + } if (stableProvider != null) { releaseProvider(stableProvider); } @@ -866,33 +1169,6 @@ public abstract class ContentResolver { return res; } - /** @hide */ - static public int modeToMode(Uri uri, String mode) throws FileNotFoundException { - int modeBits; - if ("r".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_ONLY; - } else if ("w".equals(mode) || "wt".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_TRUNCATE; - } else if ("wa".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_APPEND; - } else if ("rw".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_WRITE - | ParcelFileDescriptor.MODE_CREATE; - } else if ("rwt".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_WRITE - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_TRUNCATE; - } else { - throw new FileNotFoundException("Bad mode for " + uri + ": " - + mode); - } - return modeBits; - } - /** * Inserts a row into a table at the given URL. * @@ -1350,6 +1626,71 @@ public abstract class ContentResolver { } } + /** + * Take a persistable URI permission grant that has been offered. Once + * taken, the permission grant will be remembered across device reboots. + * Only URI permissions granted with + * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} can be persisted. If + * the grant has already been persisted, taking it again will touch + * {@link UriPermission#getPersistedTime()}. + * + * @see #getPersistedUriPermissions() + */ + public void takePersistableUriPermission(Uri uri, int modeFlags) { + try { + ActivityManagerNative.getDefault().takePersistableUriPermission(uri, modeFlags); + } catch (RemoteException e) { + } + } + + /** + * Relinquish a persisted URI permission grant. The URI must have been + * previously made persistent with + * {@link #takePersistableUriPermission(Uri, int)}. Any non-persistent + * grants to the calling package will remain intact. + * + * @see #getPersistedUriPermissions() + */ + public void releasePersistableUriPermission(Uri uri, int modeFlags) { + try { + ActivityManagerNative.getDefault().releasePersistableUriPermission(uri, modeFlags); + } catch (RemoteException e) { + } + } + + /** + * Return list of all URI permission grants that have been persisted by the + * calling app. That is, the returned permissions have been granted + * to the calling app. Only persistable grants taken with + * {@link #takePersistableUriPermission(Uri, int)} are returned. + * + * @see #takePersistableUriPermission(Uri, int) + * @see #releasePersistableUriPermission(Uri, int) + */ + public List getPersistedUriPermissions() { + try { + return ActivityManagerNative.getDefault() + .getPersistedUriPermissions(mPackageName, true).getList(); + } catch (RemoteException e) { + throw new RuntimeException("Activity manager has died", e); + } + } + + /** + * Return list of all persisted URI permission grants that are hosted by the + * calling app. That is, the returned permissions have been granted + * from the calling app. Only grants taken with + * {@link #takePersistableUriPermission(Uri, int)} are returned. + */ + public List getOutgoingPersistedUriPermissions() { + try { + return ActivityManagerNative.getDefault() + .getPersistedUriPermissions(mPackageName, false).getList(); + } catch (RemoteException e) { + throw new RuntimeException("Activity manager has died", e); + } + } + /** * Start an asynchronous sync operation. If you want to monitor the progress * of the sync you may register a SyncObserver. Only values of the following @@ -1361,6 +1702,8 @@ public abstract class ContentResolver { *

  • Float
  • *
  • Double
  • *
  • String
  • + *
  • Account
  • + *
  • null
  • * * * @param uri the uri of the provider to sync or null to sync all providers. @@ -1392,6 +1735,8 @@ public abstract class ContentResolver { *
  • Float
  • *
  • Double
  • *
  • String
  • + *
  • Account
  • + *
  • null
  • * * * @param account which account should be synced @@ -1399,10 +1744,30 @@ public abstract class ContentResolver { * @param extras any extras to pass to the SyncAdapter. */ public static void requestSync(Account account, String authority, Bundle extras) { - validateSyncExtrasBundle(extras); + if (extras == null) { + throw new IllegalArgumentException("Must specify extras."); + } + SyncRequest request = + new SyncRequest.Builder() + .setSyncAdapter(account, authority) + .setExtras(extras) + .syncOnce() + .build(); + requestSync(request); + } + + /** + * 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 { - getContentService().requestSync(account, authority, extras); - } catch (RemoteException e) { + getContentService().sync(request); + } catch(RemoteException e) { + // Shouldn't happen. } } @@ -1532,6 +1897,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. + *

    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. * *

    This method requires the caller to hold the permission * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. @@ -1546,12 +1914,6 @@ public abstract class ContentResolver { public static void addPeriodicSync(Account account, String authority, Bundle extras, long pollFrequency) { validateSyncExtrasBundle(extras); - if (account == null) { - throw new IllegalArgumentException("account must not be null"); - } - if (authority == null) { - throw new IllegalArgumentException("authority must not be null"); - } if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false) || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false) || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false) @@ -1562,7 +1924,7 @@ public abstract class ContentResolver { throw new IllegalArgumentException("illegal extras were set"); } try { - getContentService().addPeriodicSync(account, authority, extras, pollFrequency); + getContentService().addPeriodicSync(account, authority, extras, pollFrequency); } catch (RemoteException e) { // exception ignored; if this is thrown then it means the runtime is in the midst of // being restarted @@ -1581,12 +1943,6 @@ public abstract class ContentResolver { */ public static void removePeriodicSync(Account account, String authority, Bundle extras) { validateSyncExtrasBundle(extras); - if (account == null) { - throw new IllegalArgumentException("account must not be null"); - } - if (authority == null) { - throw new IllegalArgumentException("authority must not be null"); - } try { getContentService().removePeriodicSync(account, authority, extras); } catch (RemoteException e) { @@ -1604,12 +1960,6 @@ public abstract class ContentResolver { * @return a list of PeriodicSync objects. This list may be empty but will never be null. */ public static List getPeriodicSyncs(Account account, String authority) { - if (account == null) { - throw new IllegalArgumentException("account must not be null"); - } - if (authority == null) { - throw new IllegalArgumentException("authority must not be null"); - } try { return getContentService().getPeriodicSyncs(account, authority); } catch (RemoteException e) { @@ -1832,6 +2182,7 @@ public abstract class ContentResolver { private void maybeLogQueryToEventLog(long durationMillis, Uri uri, String[] projection, String selection, String sortOrder) { + if (!ENABLE_CONTENT_SAMPLE) return; int samplePercent = samplePercentForDuration(durationMillis); if (samplePercent < 100) { synchronized (mRandom) { @@ -1871,6 +2222,7 @@ public abstract class ContentResolver { private void maybeLogUpdateToEventLog( long durationMillis, Uri uri, String operation, String selection) { + if (!ENABLE_CONTENT_SAMPLE) return; int samplePercent = samplePercentForDuration(durationMillis); if (samplePercent < 100) { synchronized (mRandom) { @@ -1935,7 +2287,7 @@ public abstract class ContentResolver { private final class ParcelFileDescriptorInner extends ParcelFileDescriptor { private final IContentProvider mContentProvider; - private boolean mReleaseProviderFlag = false; + private boolean mProviderReleased; ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) { super(pfd); @@ -1943,18 +2295,10 @@ public abstract class ContentResolver { } @Override - public void close() throws IOException { - if(!mReleaseProviderFlag) { - super.close(); + public void releaseResources() { + if (!mProviderReleased) { ContentResolver.this.releaseProvider(mContentProvider); - mReleaseProviderFlag = true; - } - } - - @Override - protected void finalize() throws Throwable { - if (!mReleaseProviderFlag) { - close(); + mProviderReleased = true; } } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5bd28b983abba864beaa2595ca1b82bed39a55a9..2e4e209b5c1540239099e31a436c816ba8fa9cbf 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -30,12 +30,14 @@ import android.graphics.drawable.Drawable; import android.media.MediaScannerConnection.OnScanCompletedListener; import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.os.StatFs; import android.os.UserHandle; import android.os.UserManager; import android.util.AttributeSet; -import android.view.CompatibilityInfoHolder; +import android.view.DisplayAdjustments; import android.view.Display; import android.view.WindowManager; @@ -45,7 +47,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.List; /** * Interface to global information about an application environment. This is @@ -231,7 +232,15 @@ public abstract class Context { * tries to balance such requests from one app vs. the importantance of * keeping other apps around. */ - public static final int BIND_VISIBLE = 0x0100; + public static final int BIND_VISIBLE = 0x10000000; + + /** + * @hide + * Flag for {@link #bindService}: Consider this binding to be causing the target + * process to be showing UI, so it will be do a UI_HIDDEN memory trim when it goes + * away. + */ + public static final int BIND_SHOWING_UI = 0x20000000; /** * Flag for {@link #bindService}: Don't consider the bound service to be @@ -307,7 +316,7 @@ public abstract class Context { } /** - * Remove a {@link ComponentCallbacks} objec that was previously registered + * Remove a {@link ComponentCallbacks} object that was previously registered * with {@link #registerComponentCallbacks(ComponentCallbacks)}. */ public void unregisterComponentCallbacks(ComponentCallbacks callback) { @@ -428,6 +437,13 @@ public abstract class Context { /** @hide Return the name of the base context this context is derived from. */ public abstract String getBasePackageName(); + /** @hide Return the package name that should be used for app ops calls from + * this context. This is the same as {@link #getBasePackageName()} except in + * cases where system components are loaded into other app processes, in which + * case this will be the name of the primary package in that process (so that app + * ops uid verification will work with the name). */ + public abstract String getOpPackageName(); + /** Return the full application info for this context's package. */ public abstract ApplicationInfo getApplicationInfo(); @@ -482,7 +498,7 @@ public abstract class Context { * is always on in apps targetting Gingerbread (Android 2.3) and below, and * off by default in later versions. * - * @return Returns the single SharedPreferences instance that can be used + * @return The single {@link SharedPreferences} instance that can be used * to retrieve and modify the preference values. * * @see #MODE_PRIVATE @@ -500,7 +516,7 @@ public abstract class Context { * @param name The name of the file to open; can not contain path * separators. * - * @return FileInputStream Resulting input stream. + * @return The resulting {@link FileInputStream}. * * @see #openFileOutput * @see #fileList @@ -521,7 +537,7 @@ public abstract class Context { * {@link #MODE_WORLD_READABLE} and {@link #MODE_WORLD_WRITEABLE} to control * permissions. * - * @return FileOutputStream Resulting output stream. + * @return The resulting {@link FileOutputStream}. * * @see #MODE_APPEND * @see #MODE_PRIVATE @@ -542,8 +558,8 @@ public abstract class Context { * @param name The name of the file to delete; can not contain path * separators. * - * @return True if the file was successfully deleted; else - * false. + * @return {@code true} if the file was successfully deleted; else + * {@code false}. * * @see #openFileInput * @see #openFileOutput @@ -559,7 +575,7 @@ public abstract class Context { * @param name The name of the file for which you would like to get * its path. * - * @return Returns an absolute path to the given file. + * @return An absolute path to the given file. * * @see #openFileOutput * @see #getFilesDir @@ -571,7 +587,7 @@ public abstract class Context { * Returns the absolute path to the directory on the filesystem where * files created with {@link #openFileOutput} are stored. * - * @return Returns the path of the directory holding application files. + * @return The path of the directory holding application files. * * @see #openFileOutput * @see #getFileStreamPath @@ -580,10 +596,10 @@ public abstract class Context { public abstract File getFilesDir(); /** - * Returns the absolute path to the directory on the external filesystem + * Returns the absolute path to the directory on the primary external filesystem * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory() * Environment.getExternalStorageDirectory()}) where the application can - * place persistent files it owns. These files are private to the + * place persistent files it owns. These files are internal to the * applications, and not typically visible to the user as media. * *

    This is like {@link #getFilesDir()} in that these @@ -594,10 +610,18 @@ public abstract class Context { *

  • External files are not always available: they will disappear if the * user mounts the external storage on a computer or removes it. See the * APIs on {@link android.os.Environment} for information in the storage state. - *
  • There is no security enforced with these files. All applications - * can read and write files placed here. + *
  • There is no security enforced with these files. For example, any application + * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. * * + *

    Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to read or write to the returned path; it's always + * accessible to the calling app. This only applies to paths generated for + * package name of the calling application. To access paths belonging + * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} + * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required. + * *

    On devices with multiple users (as described by {@link UserManager}), * each user has their own isolated external storage. Applications only * have access to the external storage for the user they're running as.

    @@ -631,9 +655,6 @@ public abstract class Context { * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java * private_picture} * - *

    Writing to this path requires the - * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission.

    - * * @param type The type of files directory to return. May be null for * the root of the files directory or one of * the following Environment constants for a subdirectory: @@ -645,7 +666,7 @@ public abstract class Context { * {@link android.os.Environment#DIRECTORY_PICTURES}, or * {@link android.os.Environment#DIRECTORY_MOVIES}. * - * @return Returns the path of the directory holding application files + * @return The path of the directory holding application files * on external storage. Returns null if external storage is not currently * mounted so it could not ensure the path exists; you will need to call * this method again when it is available. @@ -656,17 +677,104 @@ public abstract class Context { public abstract File getExternalFilesDir(String type); /** - * Return the directory where this application's OBB files (if there - * are any) can be found. Note if the application does not have any OBB - * files, this directory may not exist. + * Returns absolute paths to application-specific directories on all + * external storage devices where the application can place persistent files + * it owns. These files are internal to the application, and not typically + * visible to the user as media. + *

    + * This is like {@link #getFilesDir()} in that these files will be deleted when + * the application is uninstalled, however there are some important differences: + *

      + *
    • External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + *
    • There is no security enforced with these files. + *
    + *

    + * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + *

    + * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

    + * No permissions are required to read or write to the returned paths; they + * are always accessible to the calling app. Write access outside of these + * paths on secondary external storage devices is not available. + *

    + * The first path returned is the same as {@link #getExternalFilesDir(String)}. + * Returned paths may be {@code null} if a storage device is unavailable. * - *

    On devices with multiple users (as described by {@link UserManager}), + * @see #getExternalFilesDir(String) + * @see Environment#getStorageState(File) + */ + public abstract File[] getExternalFilesDirs(String type); + + /** + * Return the primary external storage directory where this application's OBB + * files (if there are any) can be found. Note if the application does not have + * any OBB files, this directory may not exist. + *

    + * This is like {@link #getFilesDir()} in that these files will be deleted when + * the application is uninstalled, however there are some important differences: + *

      + *
    • External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + *
    • There is no security enforced with these files. For example, any application + * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
    + *

    + * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to read or write to the returned path; it's always + * accessible to the calling app. This only applies to paths generated for + * package name of the calling application. To access paths belonging + * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} + * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required. + *

    + * On devices with multiple users (as described by {@link UserManager}), * multiple users may share the same OBB storage location. Applications - * should ensure that multiple instances running under different users - * don't interfere with each other.

    + * should ensure that multiple instances running under different users don't + * interfere with each other. */ public abstract File getObbDir(); + /** + * Returns absolute paths to application-specific directories on all + * external storage devices where the application's OBB files (if there are + * any) can be found. Note if the application does not have any OBB files, + * these directories may not exist. + *

    + * This is like {@link #getFilesDir()} in that these files will be deleted when + * the application is uninstalled, however there are some important differences: + *

      + *
    • External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + *
    • There is no security enforced with these files. + *
    + *

    + * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + *

    + * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

    + * No permissions are required to read or write to the returned paths; they + * are always accessible to the calling app. Write access outside of these + * paths on secondary external storage devices is not available. + *

    + * The first path returned is the same as {@link #getObbDir()}. + * Returned paths may be {@code null} if a storage device is unavailable. + * + * @see #getObbDir() + * @see Environment#getStorageState(File) + */ + public abstract File[] getObbDirs(); + /** * Returns the absolute path to the application specific cache directory * on the filesystem. These files will be ones that get deleted first when the @@ -678,7 +786,7 @@ public abstract class Context { * for the amount of space you consume with cache files, and prune those * files when exceeding that space. * - * @return Returns the path of the directory holding application cache files. + * @return The path of the directory holding application cache files. * * @see #openFileOutput * @see #getFileStreamPath @@ -687,10 +795,11 @@ public abstract class Context { public abstract File getCacheDir(); /** - * Returns the absolute path to the directory on the external filesystem + * Returns the absolute path to the directory on the primary external filesystem * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory() * Environment.getExternalStorageDirectory()} where the application can - * place cache files it owns. + * place cache files it owns. These files are internal to the application, and + * not typically visible to the user as media. * *

    This is like {@link #getCacheDir()} in that these * files will be deleted when the application is uninstalled, however there @@ -708,18 +817,23 @@ public abstract class Context { *

  • External files are not always available: they will disappear if the * user mounts the external storage on a computer or removes it. See the * APIs on {@link android.os.Environment} for information in the storage state. - *
  • There is no security enforced with these files. All applications - * can read and write files placed here. + *
  • There is no security enforced with these files. For example, any application + * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. * * + *

    Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to read or write to the returned path; it's always + * accessible to the calling app. This only applies to paths generated for + * package name of the calling application. To access paths belonging + * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} + * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required. + * *

    On devices with multiple users (as described by {@link UserManager}), * each user has their own isolated external storage. Applications only * have access to the external storage for the user they're running as.

    * - *

    Writing to this path requires the - * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission.

    - * - * @return Returns the path of the directory holding application cache files + * @return The path of the directory holding application cache files * on external storage. Returns null if external storage is not currently * mounted so it could not ensure the path exists; you will need to call * this method again when it is available. @@ -728,6 +842,41 @@ public abstract class Context { */ public abstract File getExternalCacheDir(); + /** + * Returns absolute paths to application-specific directories on all + * external storage devices where the application can place cache files it + * owns. These files are internal to the application, and not typically + * visible to the user as media. + *

    + * This is like {@link #getCacheDir()} in that these files will be deleted when + * the application is uninstalled, however there are some important differences: + *

      + *
    • External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + *
    • There is no security enforced with these files. + *
    + *

    + * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + *

    + * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

    + * No permissions are required to read or write to the returned paths; they + * are always accessible to the calling app. Write access outside of these + * paths on secondary external storage devices is not available. + *

    + * The first path returned is the same as {@link #getExternalCacheDir()}. + * Returned paths may be {@code null} if a storage device is unavailable. + * + * @see #getExternalCacheDir() + * @see Environment#getStorageState(File) + */ + public abstract File[] getExternalCacheDirs(); + /** * Returns an array of strings naming the private files associated with * this Context's application package. @@ -754,7 +903,7 @@ public abstract class Context { * default operation, {@link #MODE_WORLD_READABLE} and * {@link #MODE_WORLD_WRITEABLE} to control permissions. * - * @return Returns a File object for the requested directory. The directory + * @return A {@link File} object for the requested directory. The directory * will have been created if it does not already exist. * * @see #openFileOutput(String, int) @@ -820,7 +969,7 @@ public abstract class Context { * @param name The name (unique in the application package) of the * database. * - * @return True if the database was successfully deleted; else false. + * @return {@code true} if the database was successfully deleted; else {@code false}. * * @see #openOrCreateDatabase */ @@ -833,7 +982,7 @@ public abstract class Context { * @param name The name of the database for which you would like to get * its path. * - * @return Returns an absolute path to the given database. + * @return An absolute path to the given database. * * @see #openOrCreateDatabase */ @@ -911,9 +1060,9 @@ public abstract class Context { * * @param intent The description of the activity to start. * - * @throws ActivityNotFoundException + * @throws ActivityNotFoundException   * - * @see {@link #startActivity(Intent, Bundle)} + * @see #startActivity(Intent, Bundle) * @see PackageManager#resolveActivity */ public abstract void startActivity(Intent intent); @@ -925,7 +1074,7 @@ public abstract class Context { * the INTERACT_ACROSS_USERS_FULL permission. * @param intent The description of the activity to start. * @param user The UserHandle of the user to start this activity for. - * @throws ActivityNotFoundException + * @throws ActivityNotFoundException   * @hide */ public void startActivityAsUser(Intent intent, UserHandle user) { @@ -952,7 +1101,7 @@ public abstract class Context { * for how to build the Bundle supplied here; there are no supported definitions * for building it manually. * - * @throws ActivityNotFoundException + * @throws ActivityNotFoundException   * * @see #startActivity(Intent) * @see PackageManager#resolveActivity @@ -969,8 +1118,8 @@ public abstract class Context { * May be null if there are no options. See {@link android.app.ActivityOptions} * for how to build the Bundle supplied here; there are no supported definitions * for building it manually. - * @param user The UserHandle of the user to start this activity for. - * @throws ActivityNotFoundException + * @param userId The UserHandle of the user to start this activity for. + * @throws ActivityNotFoundException   * @hide */ public void startActivityAsUser(Intent intent, Bundle options, UserHandle userId) { @@ -983,9 +1132,9 @@ public abstract class Context { * * @param intents An array of Intents to be started. * - * @throws ActivityNotFoundException + * @throws ActivityNotFoundException   * - * @see {@link #startActivities(Intent[], Bundle)} + * @see #startActivities(Intent[], Bundle) * @see PackageManager#resolveActivity */ public abstract void startActivities(Intent[] intents); @@ -1009,9 +1158,9 @@ public abstract class Context { * See {@link android.content.Context#startActivity(Intent, Bundle) * Context.startActivity(Intent, Bundle)} for more details. * - * @throws ActivityNotFoundException + * @throws ActivityNotFoundException   * - * @see {@link #startActivities(Intent[])} + * @see #startActivities(Intent[]) * @see PackageManager#resolveActivity */ public abstract void startActivities(Intent[] intents, Bundle options); @@ -1037,9 +1186,9 @@ public abstract class Context { * See {@link android.content.Context#startActivity(Intent, Bundle) * Context.startActivity(Intent, Bundle)} for more details. * - * @throws ActivityNotFoundException + * @throws ActivityNotFoundException   * - * @see {@link #startActivities(Intent[])} + * @see #startActivities(Intent[]) * @see PackageManager#resolveActivity */ public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { @@ -1566,9 +1715,10 @@ public abstract class Context { /** * Request that a given application service be started. The Intent - * can either contain the complete class name of a specific service - * implementation to start, or an abstract definition through the - * action and other fields of the kind of service to start. If this service + * should contain either contain the complete class name of a specific service + * implementation to start or a specific package name to target. If the + * Intent is less specified, it log a warning about this and which of the + * multiple matching services it finds and uses will be undefined. If this service * is not already running, it will be instantiated and started (creating a * process for it if needed); if it is running then it remains running. * @@ -1594,10 +1744,9 @@ public abstract class Context { *

    This function will throw {@link SecurityException} if you do not * have permission to start the given service. * - * @param service Identifies the service to be started. The Intent may - * specify either an explicit component name to start, or a logical - * description (action, category, etc) to match an - * {@link IntentFilter} published by a service. Additional values + * @param service Identifies the service to be started. The Intent must be either + * fully explicit (supplying a component name) or specify a specific package + * name it is targetted to. Additional values * may be included in the Intent extras to supply arguments along with * this specific start call. * @@ -1605,7 +1754,7 @@ public abstract class Context { * {@link ComponentName} of the actual service that was started is * returned; else if the service does not exist null is returned. * - * @throws SecurityException + * @throws SecurityException   * * @see #stopService * @see #bindService @@ -1627,15 +1776,14 @@ public abstract class Context { *

    This function will throw {@link SecurityException} if you do not * have permission to stop the given service. * - * @param service Description of the service to be stopped. The Intent may - * specify either an explicit component name to start, or a logical - * description (action, category, etc) to match an - * {@link IntentFilter} published by a service. + * @param service Description of the service to be stopped. The Intent must be either + * fully explicit (supplying a component name) or specify a specific package + * name it is targetted to. * * @return If there is a service matching the given Intent that is already - * running, then it is stopped and true is returned; else false is returned. + * running, then it is stopped and {@code true} is returned; else {@code false} is returned. * - * @throws SecurityException + * @throws SecurityException   * * @see #startService */ @@ -1686,11 +1834,11 @@ public abstract class Context { * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT}, * {@link #BIND_ALLOW_OOM_MANAGEMENT}, or * {@link #BIND_WAIVE_PRIORITY}. - * @return If you have successfully bound to the service, true is returned; - * false is returned if the connection is not made so you will not + * @return If you have successfully bound to the service, {@code true} is returned; + * {@code false} is returned if the connection is not made so you will not * receive the service object. * - * @throws SecurityException + * @throws SecurityException   * * @see #unbindService * @see #startService @@ -1742,8 +1890,8 @@ public abstract class Context { * @param arguments Additional optional arguments to pass to the * instrumentation, or null. * - * @return Returns true if the instrumentation was successfully started, - * else false if it could not be found. + * @return {@code true} if the instrumentation was successfully started, + * else {@code false} if it could not be found. */ public abstract boolean startInstrumentation(ComponentName className, String profileFile, Bundle arguments); @@ -1927,6 +2075,17 @@ public abstract class Context { */ public static final String ACCESSIBILITY_SERVICE = "accessibility"; + /** + * 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. @@ -2032,13 +2191,8 @@ public abstract class Context { public static final String UPDATE_LOCK_SERVICE = "updatelock"; /** - * Use with {@link #getSystemService} to retrieve a {@link - * android.net.NetworkManagementService} for handling management of - * system network services - * + * Constant for the internal network management service, not really a Context service. * @hide - * @see #getSystemService - * @see android.net.NetworkManagementService */ public static final String NETWORKMANAGEMENT_SERVICE = "network_management"; @@ -2218,7 +2372,7 @@ public abstract class Context { * and for controlling this device's behavior as a USB device. * * @see #getSystemService - * @see android.harware.usb.UsbManager + * @see android.hardware.usb.UsbManager */ public static final String USB_SERVICE = "usb"; @@ -2227,7 +2381,7 @@ public abstract class Context { * android.hardware.SerialManager} for access to serial ports. * * @see #getSystemService - * @see android.harware.SerialManager + * @see android.hardware.SerialManager * * @hide */ @@ -2251,17 +2405,6 @@ public abstract class Context { */ public static final String DISPLAY_SERVICE = "display"; - /** - * Use with {@link #getSystemService} to retrieve a - * {@link android.os.SchedulingPolicyService} for managing scheduling policy. - * - * @see #getSystemService - * @see android.os.SchedulingPolicyService - * - * @hide - */ - public static final String SCHEDULING_POLICY_SERVICE = "scheduling_policy"; - /** * Use with {@link #getSystemService} to retrieve a * {@link android.os.UserManager} for managing users on devices that support multiple users. @@ -2278,10 +2421,38 @@ public abstract class Context { * * @see #getSystemService * @see android.app.AppOpsManager + */ + public static final String APP_OPS_SERVICE = "appops"; + + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.camera2.CameraManager} for interacting with + * camera devices. * + * @see #getSystemService + * @see android.hardware.camera2.CameraManager * @hide */ - public static final String APP_OPS_SERVICE = "appops"; + public static final String CAMERA_SERVICE = "camera"; + + /** + * {@link android.print.PrintManager} for printing and managing + * printers and print tasks. + * + * @see #getSystemService + * @see android.print.PrintManager + */ + public static final String PRINT_SERVICE = "print"; + + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.ConsumerIrManager} for transmitting infrared + * signals from the device. + * + * @see #getSystemService + * @see android.hardware.ConsumerIrManager + */ + public static final String CONSUMER_IR_SERVICE = "consumer_ir"; /** * Determine whether the given permission is allowed for a particular @@ -2292,7 +2463,7 @@ public abstract class Context { * @param uid The user ID being checked against. A uid of 0 is the root * user, which will pass every permission check. * - * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the given + * @return {@link PackageManager#PERMISSION_GRANTED} if the given * pid/uid is allowed that permission, or * {@link PackageManager#PERMISSION_DENIED} if it is not. * @@ -2314,7 +2485,7 @@ public abstract class Context { * * @param permission The name of the permission being checked. * - * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the calling + * @return {@link PackageManager#PERMISSION_GRANTED} if the calling * pid/uid is allowed that permission, or * {@link PackageManager#PERMISSION_DENIED} if it is not. * @@ -2332,7 +2503,7 @@ public abstract class Context { * * @param permission The name of the permission being checked. * - * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the calling + * @return {@link PackageManager#PERMISSION_GRANTED} if the calling * pid/uid is allowed that permission, or * {@link PackageManager#PERMISSION_DENIED} if it is not. * @@ -2434,7 +2605,7 @@ public abstract class Context { * Remove all permissions to access a particular content provider Uri * that were previously added with {@link #grantUriPermission}. The given * Uri will match all previously granted Uris that are the same or a - * sub-path of the given Uri. That is, revoking "content://foo/one" will + * sub-path of the given Uri. That is, revoking "content://foo/target" will * revoke both "content://foo/target" and "content://foo/target/sub", but not * "content://foo". * @@ -2464,7 +2635,7 @@ public abstract class Context { * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. * - * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the given + * @return {@link PackageManager#PERMISSION_GRANTED} if the given * pid/uid is allowed to access that uri, or * {@link PackageManager#PERMISSION_DENIED} if it is not. * @@ -2487,7 +2658,7 @@ public abstract class Context { * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. * - * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the caller + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller * is allowed to access that uri, or * {@link PackageManager#PERMISSION_DENIED} if it is not. * @@ -2506,7 +2677,7 @@ public abstract class Context { * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. * - * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the caller + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller * is allowed to access that uri, or * {@link PackageManager#PERMISSION_DENIED} if it is not. * @@ -2532,7 +2703,7 @@ public abstract class Context { * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. * - * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the caller + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller * is allowed to access that uri or holds one of the given permissions, or * {@link PackageManager#PERMISSION_DENIED} if it is not. */ @@ -2676,11 +2847,11 @@ public abstract class Context { * @param flags Option flags, one of {@link #CONTEXT_INCLUDE_CODE} * or {@link #CONTEXT_IGNORE_SECURITY}. * - * @return A Context for the application. + * @return A {@link Context} for the application. * - * @throws java.lang.SecurityException + * @throws SecurityException   * @throws PackageManager.NameNotFoundException if there is no application with - * the given package name + * the given package name. */ public abstract Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException; @@ -2717,7 +2888,7 @@ public abstract class Context { * orientation change), the resources of this context will also change except * for those that have been explicitly overridden with a value here. * - * @return A Context with the given configuration override. + * @return A {@link Context} with the given configuration override. */ public abstract Context createConfigurationContext(Configuration overrideConfiguration); @@ -2737,25 +2908,25 @@ public abstract class Context { * for whose metrics the Context's resources should be tailored and upon which * new windows should be shown. * - * @return A Context for the display. + * @return A {@link Context} for the display. */ public abstract Context createDisplayContext(Display display); /** - * Gets the compatibility info holder for this context. This information - * is provided on a per-application basis and is used to simulate lower density - * display metrics for legacy applications. + * Gets the display adjustments holder for this context. This information + * is provided on a per-application or activity basis and is used to simulate lower density + * display metrics for legacy applications and restricted screen sizes. * * @param displayId The display id for which to get compatibility info. * @return The compatibility info holder, or null if not required by the application. * @hide */ - public abstract CompatibilityInfoHolder getCompatibilityInfo(int displayId); + public abstract DisplayAdjustments getDisplayAdjustments(int displayId); /** * Indicates whether this Context is restricted. * - * @return True if this Context is restricted, false otherwise. + * @return {@code true} if this Context is restricted, {@code false} otherwise. * * @see #CONTEXT_RESTRICTED */ diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 2f1bf8ce2401390dcc467ee87902ce31cf55812b..a708dad6bf3ec92cb273f57829c7eb019ec1539d 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -35,7 +35,7 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; -import android.view.CompatibilityInfoHolder; +import android.view.DisplayAdjustments; import android.view.Display; import java.io.File; @@ -141,6 +141,12 @@ public class ContextWrapper extends Context { return mBase.getBasePackageName(); } + /** @hide */ + @Override + public String getOpPackageName() { + return mBase.getOpPackageName(); + } + @Override public ApplicationInfo getApplicationInfo() { return mBase.getApplicationInfo(); @@ -203,12 +209,22 @@ public class ContextWrapper extends Context { public File getExternalFilesDir(String type) { return mBase.getExternalFilesDir(type); } - + + @Override + public File[] getExternalFilesDirs(String type) { + return mBase.getExternalFilesDirs(type); + } + @Override public File getObbDir() { return mBase.getObbDir(); } - + + @Override + public File[] getObbDirs() { + return mBase.getObbDirs(); + } + @Override public File getCacheDir() { return mBase.getCacheDir(); @@ -219,6 +235,11 @@ public class ContextWrapper extends Context { return mBase.getExternalCacheDir(); } + @Override + public File[] getExternalCacheDirs() { + return mBase.getExternalCacheDirs(); + } + @Override public File getDir(String name, int mode) { return mBase.getDir(name, mode); @@ -646,7 +667,7 @@ public class ContextWrapper extends Context { /** @hide */ @Override - public CompatibilityInfoHolder getCompatibilityInfo(int displayId) { - return mBase.getCompatibilityInfo(displayId); + public DisplayAdjustments getDisplayAdjustments(int displayId) { + return mBase.getDisplayAdjustments(displayId); } } diff --git a/core/java/android/content/IAnonymousSyncAdapter.aidl b/core/java/android/content/IAnonymousSyncAdapter.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a80cea3d19df5c5b5b0209ecc5db885af8a8f504 --- /dev/null +++ b/core/java/android/content/IAnonymousSyncAdapter.aidl @@ -0,0 +1,45 @@ +/* + * 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.os.Bundle; +import android.content.ISyncContext; + +/** + * Interface to define an anonymous service that is extended by developers + * in order to perform anonymous syncs (syncs without an Account or Content + * Provider specified). See {@link android.content.AbstractThreadedSyncAdapter}. + * {@hide} + */ +oneway interface IAnonymousSyncAdapter { + + /** + * Initiate a sync. SyncAdapter-specific parameters may be specified in + * extras, which is guaranteed to not be null. + * + * @param syncContext the ISyncContext used to indicate the progress of the sync. When + * the sync is finished (successfully or not) ISyncContext.onFinished() must be called. + * @param extras SyncAdapter-specific parameters. + * + */ + void startSync(ISyncContext syncContext, in Bundle extras); + + /** + * Cancel the currently ongoing sync. + */ + void cancelSync(ISyncContext syncContext); + +} diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 62b79f0a157e2660cd5c042c40d03b667149478e..f92a4044090419679d2396b502fe7f7d14e1fe15 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -46,9 +46,11 @@ public interface IContentProvider extends IInterface { throws RemoteException; public int update(String callingPkg, Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException; - public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode) + public ParcelFileDescriptor openFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; - public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode) + public AssetFileDescriptor openAssetFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; public ContentProviderResult[] applyBatch(String callingPkg, ArrayList operations) @@ -57,10 +59,13 @@ public interface IContentProvider extends IInterface { throws RemoteException; public ICancellationSignal createCancellationSignal() throws RemoteException; + public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException; + public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException; + // Data interchange. public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException; public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType, - Bundle opts) throws RemoteException, FileNotFoundException; + Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException; /* IPC constants */ static final String descriptor = "android.content.IContentProvider"; @@ -78,4 +83,6 @@ public interface IContentProvider extends IInterface { static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21; static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22; static final int CREATE_CANCELATION_SIGNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 23; + static final int CANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 24; + static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25; } diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl index f956bcfe25976f66025c68187254a1b4f85bd51f..9ad5a19b2e4a2d3d74e255feaa76f4b3ccd7672f 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -20,6 +20,7 @@ import android.accounts.Account; import android.content.SyncInfo; import android.content.ISyncStatusObserver; import android.content.SyncAdapterType; +import android.content.SyncRequest; import android.content.SyncStatusInfo; import android.content.PeriodicSync; import android.net.Uri; @@ -54,6 +55,7 @@ interface IContentService { int userHandle); void requestSync(in Account account, String authority, in Bundle extras); + void sync(in SyncRequest request); void cancelSync(in Account account, String authority); /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 330b7e5c2df3eeed40bf3a13ca5e8de3386d1954..a289649b66cbffcdca32d06115317fb7c2b9185b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -16,6 +16,8 @@ package android.content; +import android.content.pm.ApplicationInfo; +import android.util.ArraySet; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -33,6 +35,9 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.StrictMode; +import android.provider.DocumentsContract; +import android.provider.DocumentsProvider; +import android.provider.OpenableColumns; import android.util.AttributeSet; import android.util.Log; @@ -42,8 +47,7 @@ import java.io.IOException; import java.io.Serializable; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Set; @@ -1150,9 +1154,9 @@ public class Intent implements Parcelable, Cloneable { /** * Activity Action: Perform assist action. *

    - * Input: {@link #EXTRA_ASSIST_PACKAGE} and {@link #EXTRA_ASSIST_CONTEXT} can provide - * additional optional contextual information about where the user was when they requested - * the assist. + * Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide + * additional optional contextual information about where the user was when they + * requested the assist. * Output: nothing. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @@ -1161,9 +1165,9 @@ public class Intent implements Parcelable, Cloneable { /** * Activity Action: Perform voice assist action. *

    - * Input: {@link #EXTRA_ASSIST_PACKAGE} and {@link #EXTRA_ASSIST_CONTEXT} can provide - * additional optional contextual information about where the user was when they requested - * the voice assist. + * Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide + * additional optional contextual information about where the user was when they + * requested the voice assist. * Output: nothing. * @hide */ @@ -1171,18 +1175,16 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; /** - * An optional field on {@link #ACTION_ASSIST} - * containing the name of the current foreground application package at the time - * the assist was invoked. + * An optional field on {@link #ACTION_ASSIST} containing the name of the current foreground + * application package at the time the assist was invoked. */ public static final String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE"; /** - * An optional field on {@link #ACTION_ASSIST} - * containing additional contextual information supplied by the current - * foreground app at the time of the assist request. This is a {@link Bundle} of - * additional data. + * An optional field on {@link #ACTION_ASSIST} and containing additional contextual + * information supplied by the current foreground app at the time of the assist request. + * This is a {@link Bundle} of additional data. */ public static final String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT"; @@ -1459,7 +1461,7 @@ public class Intent implements Parcelable, Cloneable { /** * Broadcast Action: The current time has changed. Sent every * minute. You can not receive this through components declared - * in manifests, only by exlicitly registering for it with + * in manifests, only by explicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. * @@ -1897,6 +1899,11 @@ public class Intent implements Parcelable, Cloneable { * *

    This is a protected intent that can only be sent * by the system. + *

    May include the following extras: + *

      + *
    • {@link #EXTRA_SHUTDOWN_USERSPACE_ONLY} a boolean that is set to true if this + * shutdown is only for userspace processes. If not set, assumed to be false. + *
    */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; @@ -2440,6 +2447,21 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_GET_RESTRICTION_ENTRIES = "android.intent.action.GET_RESTRICTION_ENTRIES"; + /** + * @hide + * Activity to challenge the user for a PIN that was configured when setting up + * restrictions. Restrictions include blocking of apps and preventing certain user operations, + * controlled by {@link android.os.UserManager#setUserRestrictions(Bundle). + * Launch the activity using + * {@link android.app.Activity#startActivityForResult(Intent, int)} and check if the + * result is {@link android.app.Activity#RESULT_OK} for a successful response to the + * challenge.

    + * Before launching this activity, make sure that there is a PIN in effect, by calling + * {@link android.os.UserManager#hasRestrictionsChallenge()}. + */ + public static final String ACTION_RESTRICTIONS_CHALLENGE = + "android.intent.action.RESTRICTIONS_CHALLENGE"; + /** * Sent the first time a user is starting, to allow system apps to * perform one time initialization. (This will not be seen by third @@ -2599,6 +2621,81 @@ public class Intent implements Parcelable, Cloneable { */ public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON"; + /** + * Activity Action: Allow the user to select and return one or more existing + * documents. When invoked, the system will display the various + * {@link DocumentsProvider} instances installed on the device, letting the + * user interactively navigate through them. These documents include local + * media, such as photos and video, and documents provided by installed + * cloud storage providers. + *

    + * Each document is represented as a {@code content://} URI backed by a + * {@link DocumentsProvider}, which can be opened as a stream with + * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for + * {@link android.provider.DocumentsContract.Document} metadata. + *

    + * All selected documents are returned to the calling application with + * persistable read and write permission grants. If you want to maintain + * access to the documents across device reboots, you need to explicitly + * take the persistable permissions using + * {@link ContentResolver#takePersistableUriPermission(Uri, int)}. + *

    + * Callers can restrict document selection to a specific kind of data, such + * as photos, by setting one or more MIME types in + * {@link #EXTRA_MIME_TYPES}. + *

    + * If the caller can handle multiple returned items (the user performing + * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE} + * to indicate this. + *

    + * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that + * returned URIs can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. + *

    + * Output: The URI of the item that was picked. This must be a + * {@code content://} URI so that any receiver can access it. If multiple + * documents were selected, they are returned in {@link #getClipData()}. + * + * @see DocumentsContract + * @see #ACTION_CREATE_DOCUMENT + * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT"; + + /** + * Activity Action: Allow the user to create a new document. When invoked, + * the system will display the various {@link DocumentsProvider} instances + * installed on the device, letting the user navigate through them. The + * returned document may be a newly created document with no content, or it + * may be an existing document with the requested MIME type. + *

    + * Each document is represented as a {@code content://} URI backed by a + * {@link DocumentsProvider}, which can be opened as a stream with + * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for + * {@link android.provider.DocumentsContract.Document} metadata. + *

    + * Callers must indicate the concrete MIME type of the document being + * created by setting {@link #setType(String)}. This MIME type cannot be + * changed after the document is created. + *

    + * Callers can provide an initial display name through {@link #EXTRA_TITLE}, + * but the user may change this value before creating the file. + *

    + * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that + * returned URIs can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. + *

    + * Output: The URI of the item that was created. This must be a + * {@code content://} URI so that any receiver can access it. + * + * @see DocumentsContract + * @see #ACTION_OPEN_DOCUMENT + * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -2607,7 +2704,7 @@ public class Intent implements Parcelable, Cloneable { * Set if the activity should be an option for the default action * (center press) to perform on a piece of data. Setting this will * hide from the user any activities without it set when performing an - * action on some data. Note that this is normal -not- set in the + * action on some data. Note that this is normally -not- set in the * Intent when initiating an action -- it is for use in intent filters * specified in packages. */ @@ -2718,11 +2815,16 @@ public class Intent implements Parcelable, Cloneable { * experience). */ public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE"; + /** - * Used to indicate that a GET_CONTENT intent only wants URIs that can be opened with - * ContentResolver.openInputStream. Openable URIs must support the columns in - * {@link android.provider.OpenableColumns} - * when queried, though it is allowable for those columns to be blank. + * Used to indicate that an intent only wants URIs that can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. Openable URIs + * must support at least the columns defined in {@link OpenableColumns} when + * queried. + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + * @see #ACTION_CREATE_DOCUMENT */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_OPENABLE = "android.intent.category.OPENABLE"; @@ -3149,27 +3251,32 @@ public class Intent implements Parcelable, Cloneable { "android.intent.extra.client_intent"; /** - * Used to indicate that a {@link #ACTION_GET_CONTENT} intent should only return - * data that is on the local device. This is a boolean extra; the default - * is false. If true, an implementation of ACTION_GET_CONTENT should only allow - * the user to select media that is already on the device, not requiring it - * be downloaded from a remote service when opened. Another way to look - * at it is that such content should generally have a "_data" column to the - * path of the content on local external storage. + * Extra used to indicate that an intent should only return data that is on + * the local device. This is a boolean extra; the default is false. If true, + * an implementation should only allow the user to select data that is + * already on the device, not requiring it be downloaded from a remote + * service when opened. + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + * @see #ACTION_CREATE_DOCUMENT */ public static final String EXTRA_LOCAL_ONLY = - "android.intent.extra.LOCAL_ONLY"; + "android.intent.extra.LOCAL_ONLY"; /** - * Used to indicate that a {@link #ACTION_GET_CONTENT} intent can allow the - * user to select and return multiple items. This is a boolean extra; the default - * is false. If true, an implementation of ACTION_GET_CONTENT is allowed to - * present the user with a UI where they can pick multiple items that are all - * returned to the caller. When this happens, they should be returned as - * the {@link #getClipData()} part of the result Intent. + * Extra used to indicate that an intent can allow the user to select and + * return multiple items. This is a boolean extra; the default is false. If + * true, an implementation is allowed to present the user with a UI where + * they can pick multiple items that are all returned to the caller. When + * this happens, they should be returned as the {@link #getClipData()} part + * of the result Intent. + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT */ public static final String EXTRA_ALLOW_MULTIPLE = - "android.intent.extra.ALLOW_MULTIPLE"; + "android.intent.extra.ALLOW_MULTIPLE"; /** * The userHandle carried with broadcast intents related to addition, removal and switching of @@ -3202,13 +3309,35 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent"; + /** + * Extra used to communicate a set of acceptable MIME types. The type of the + * extra is {@code String[]}. Values may be a combination of concrete MIME + * types (such as "image/png") and/or partial MIME types (such as + * "audio/*"). + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + */ + public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES"; + + /** + * 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. + * 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"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). /** * If set, the recipient of this Intent will be granted permission to - * perform read operations on the Uri in the Intent's data and any URIs + * perform read operations on the URI in the Intent's data and any URIs * specified in its ClipData. When applying to an Intent's ClipData, * all URIs as well as recursive traversals through data or other ClipData * in Intent items will be granted; only the grant flags of the top-level @@ -3217,7 +3346,7 @@ public class Intent implements Parcelable, Cloneable { public static final int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001; /** * If set, the recipient of this Intent will be granted permission to - * perform write operations on the Uri in the Intent's data and any URIs + * perform write operations on the URI in the Intent's data and any URIs * specified in its ClipData. When applying to an Intent's ClipData, * all URIs as well as recursive traversals through data or other ClipData * in Intent items will be granted; only the grant flags of the top-level @@ -3250,6 +3379,22 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020; + /** + * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant can be + * persisted across device reboots until explicitly revoked with + * {@link Context#revokeUriPermission(Uri, int)}. This flag only offers the + * grant for possible persisting; the receiving application must call + * {@link ContentResolver#takePersistableUriPermission(Uri, int)} to + * actually persist. + * + * @see ContentResolver#takePersistableUriPermission(Uri, int) + * @see ContentResolver#releasePersistableUriPermission(Uri, int) + * @see ContentResolver#getPersistedUriPermissions() + * @see ContentResolver#getOutgoingPersistedUriPermissions() + */ + public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040; + /** * If set, the new activity is not kept in the history stack. As soon as * the user navigates away from it, the activity is finished. This may also @@ -3491,6 +3636,12 @@ public class Intent implements Parcelable, Cloneable { * background priority class. */ public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000; + /** + * If this is an ordered broadcast, don't allow receivers to abort the broadcast. + * They can still propagate results through to later receivers, but they can not prevent + * later receivers from seeing the broadcast. + */ + public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000; /** * If set, when sending a broadcast before boot has completed only * registered receivers will be called -- no BroadcastReceiver components @@ -3504,14 +3655,14 @@ public class Intent implements Parcelable, Cloneable { * * @hide */ - public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x08000000; + public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000; /** * Set when this broadcast is for a boot upgrade, a special mode that * allows the broadcast to be sent before the system is ready and launches * the app process with no providers running in it. * @hide */ - public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x04000000; + public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x02000000; /** * @hide Flags that can't be changed with PendingIntent. @@ -3542,7 +3693,7 @@ public class Intent implements Parcelable, Cloneable { private String mPackage; private ComponentName mComponent; private int mFlags; - private HashSet mCategories; + private ArraySet mCategories; private Bundle mExtras; private Rect mSourceBounds; private Intent mSelector; @@ -3567,7 +3718,7 @@ public class Intent implements Parcelable, Cloneable { this.mComponent = o.mComponent; this.mFlags = o.mFlags; if (o.mCategories != null) { - this.mCategories = new HashSet(o.mCategories); + this.mCategories = new ArraySet(o.mCategories); } if (o.mExtras != null) { this.mExtras = new Bundle(o.mExtras); @@ -3595,7 +3746,7 @@ public class Intent implements Parcelable, Cloneable { this.mPackage = o.mPackage; this.mComponent = o.mComponent; if (o.mCategories != null) { - this.mCategories = new HashSet(o.mCategories); + this.mCategories = new ArraySet(o.mCategories); } } @@ -4952,6 +5103,39 @@ public class Intent implements Parcelable, Cloneable { return ai; } + /** + * Special function for use by the system to resolve service + * intents to system apps. Throws an exception if there are + * multiple potential matches to the Intent. Returns null if + * there are no matches. + * @hide + */ + public ComponentName resolveSystemService(PackageManager pm, int flags) { + if (mComponent != null) { + return mComponent; + } + + List results = pm.queryIntentServices(this, flags); + if (results == null) { + return null; + } + ComponentName comp = null; + for (int i=0; i(); + mCategories = new ArraySet(); } mCategories.add(category.intern()); return this; @@ -6303,7 +6487,7 @@ public class Intent implements Parcelable, Cloneable { if (other.mCategories != null && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) { if (other.mCategories != null) { - mCategories = new HashSet(other.mCategories); + mCategories = new ArraySet(other.mCategories); } changes |= FILL_IN_CATEGORIES; } @@ -6574,12 +6758,9 @@ public class Intent implements Parcelable, Cloneable { } first = false; b.append("cat=["); - Iterator i = mCategories.iterator(); - boolean didone = false; - while (i.hasNext()) { - if (didone) b.append(","); - didone = true; - b.append(i.next()); + for (int i=0; i 0) b.append(','); + b.append(mCategories.valueAt(i)); } b.append("]"); } @@ -6737,8 +6918,8 @@ public class Intent implements Parcelable, Cloneable { uri.append("action=").append(Uri.encode(mAction)).append(';'); } if (mCategories != null) { - for (String category : mCategories) { - uri.append("category=").append(Uri.encode(category)).append(';'); + for (int i=0; i mActions; private ArrayList mCategories = null; private ArrayList mDataSchemes = null; + private ArrayList mDataSchemeSpecificParts = null; private ArrayList mDataAuthorities = null; private ArrayList mDataPaths = null; private ArrayList mDataTypes = null; @@ -395,6 +411,9 @@ public class IntentFilter implements Parcelable { if (o.mDataSchemes != null) { mDataSchemes = new ArrayList(o.mDataSchemes); } + if (o.mDataSchemeSpecificParts != null) { + mDataSchemeSpecificParts = new ArrayList(o.mDataSchemeSpecificParts); + } if (o.mDataAuthorities != null) { mDataAuthorities = new ArrayList(o.mDataAuthorities); } @@ -698,6 +717,88 @@ public class IntentFilter implements Parcelable { } }; + /** + * Add a new Intent data "scheme specific part" to match against. The filter must + * include one or more schemes (via {@link #addDataScheme}) for the + * scheme specific part to be considered. If any scheme specific parts are + * included in the filter, then an Intent's data must match one of + * them. If no scheme specific parts are included, then only the scheme must match. + * + *

    The "scheme specific part" that this matches against is the string returned + * by {@link android.net.Uri#getSchemeSpecificPart() Uri.getSchemeSpecificPart}. + * For Uris that contain a path, this kind of matching is not generally of interest, + * since {@link #addDataAuthority(String, String)} and + * {@link #addDataPath(String, int)} can provide a better mechanism for matching + * them. However, for Uris that do not contain a path, the authority and path + * are empty, so this is the only way to match against the non-scheme part.

    + * + * @param ssp Either a raw string that must exactly match the scheme specific part + * path, or a simple pattern, depending on type. + * @param type Determines how ssp will be compared to + * determine a match: either {@link PatternMatcher#PATTERN_LITERAL}, + * {@link PatternMatcher#PATTERN_PREFIX}, or + * {@link PatternMatcher#PATTERN_SIMPLE_GLOB}. + * + * @see #matchData + * @see #addDataScheme + */ + public final void addDataSchemeSpecificPart(String ssp, int type) { + addDataSchemeSpecificPart(new PatternMatcher(ssp, type)); + } + + /** @hide */ + public final void addDataSchemeSpecificPart(PatternMatcher ssp) { + if (mDataSchemeSpecificParts == null) { + mDataSchemeSpecificParts = new ArrayList(); + } + mDataSchemeSpecificParts.add(ssp); + } + + /** + * Return the number of data scheme specific parts in the filter. + */ + public final int countDataSchemeSpecificParts() { + return mDataSchemeSpecificParts != null ? mDataSchemeSpecificParts.size() : 0; + } + + /** + * Return a data scheme specific part in the filter. + */ + public final PatternMatcher getDataSchemeSpecificPart(int index) { + return mDataSchemeSpecificParts.get(index); + } + + /** + * Is the given data scheme specific part included in the filter? Note that if the + * filter does not include any scheme specific parts, false will always be + * returned. + * + * @param data The scheme specific part that is being looked for. + * + * @return Returns true if the data string matches a scheme specific part listed in the + * filter. + */ + public final boolean hasDataSchemeSpecificPart(String data) { + if (mDataSchemeSpecificParts == null) { + return false; + } + final int numDataSchemeSpecificParts = mDataSchemeSpecificParts.size(); + for (int i = 0; i < numDataSchemeSpecificParts; i++) { + final PatternMatcher pe = mDataSchemeSpecificParts.get(i); + if (pe.match(data)) { + return true; + } + } + return false; + } + + /** + * Return an iterator over the filter's data scheme specific parts. + */ + public final Iterator schemeSpecificPartsIterator() { + return mDataSchemeSpecificParts != null ? mDataSchemeSpecificParts.iterator() : null; + } + /** * Add a new Intent data authority to match against. The filter must * include one or more schemes (via {@link #addDataScheme}) for the @@ -720,10 +821,15 @@ public class IntentFilter implements Parcelable { * @see #addDataScheme */ public final void addDataAuthority(String host, String port) { + if (port != null) port = port.intern(); + addDataAuthority(new AuthorityEntry(host.intern(), port)); + } + + /** @hide */ + public final void addDataAuthority(AuthorityEntry ent) { if (mDataAuthorities == null) mDataAuthorities = new ArrayList(); - if (port != null) port = port.intern(); - mDataAuthorities.add(new AuthorityEntry(host.intern(), port)); + mDataAuthorities.add(ent); } /** @@ -788,8 +894,13 @@ public class IntentFilter implements Parcelable { * @see #addDataAuthority */ public final void addDataPath(String path, int type) { + addDataPath(new PatternMatcher(path.intern(), type)); + } + + /** @hide */ + public final void addDataPath(PatternMatcher path) { if (mDataPaths == null) mDataPaths = new ArrayList(); - mDataPaths.add(new PatternMatcher(path.intern(), type)); + mDataPaths.add(path); } /** @@ -867,7 +978,11 @@ public class IntentFilter implements Parcelable { * Match this filter against an Intent's data (type, scheme and path). If * the filter does not specify any types and does not specify any * schemes/paths, the match will only succeed if the intent does not - * also specify a type or data. + * also specify a type or data. If the filter does not specify any schemes, + * it will implicitly match intents with no scheme, or the schemes "content:" + * or "file:" (basically performing a MIME-type only match). If the filter + * does not specify any MIME types, the Intent also must not specify a MIME + * type. * *

    Be aware that to match against an authority, you must also specify a base * scheme the authority is in. To match against a data path, both a scheme @@ -900,8 +1015,6 @@ public class IntentFilter implements Parcelable { public final int matchData(String type, String scheme, Uri data) { final ArrayList types = mDataTypes; final ArrayList schemes = mDataSchemes; - final ArrayList authorities = mDataAuthorities; - final ArrayList paths = mDataPaths; int match = MATCH_CATEGORY_EMPTY; @@ -917,20 +1030,34 @@ public class IntentFilter implements Parcelable { return NO_MATCH_DATA; } - if (authorities != null) { - int authMatch = matchDataAuthority(data); - if (authMatch >= 0) { - if (paths == null) { - match = authMatch; - } else if (hasDataPath(data.getPath())) { - match = MATCH_CATEGORY_PATH; + final ArrayList schemeSpecificParts = mDataSchemeSpecificParts; + if (schemeSpecificParts != null) { + match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart()) + ? MATCH_CATEGORY_SCHEME_SPECIFIC_PART : NO_MATCH_DATA; + } + if (match != MATCH_CATEGORY_SCHEME_SPECIFIC_PART) { + // If there isn't any matching ssp, we need to match an authority. + final ArrayList authorities = mDataAuthorities; + if (authorities != null) { + int authMatch = matchDataAuthority(data); + if (authMatch >= 0) { + final ArrayList paths = mDataPaths; + if (paths == null) { + match = authMatch; + } else if (hasDataPath(data.getPath())) { + match = MATCH_CATEGORY_PATH; + } else { + return NO_MATCH_DATA; + } } else { return NO_MATCH_DATA; } - } else { - return NO_MATCH_DATA; } } + // If neither an ssp nor an authority matched, we're done. + if (match == NO_MATCH_DATA) { + return NO_MATCH_DATA; + } } else { // Special case: match either an Intent with no data URI, // or with a scheme: URI. This is to give a convenience for @@ -1173,6 +1300,23 @@ public class IntentFilter implements Parcelable { serializer.attribute(null, NAME_STR, mDataSchemes.get(i)); serializer.endTag(null, SCHEME_STR); } + N = countDataSchemeSpecificParts(); + for (int i=0; i it = mDataSchemeSpecificParts.iterator(); + while (it.hasNext()) { + PatternMatcher pe = it.next(); + sb.setLength(0); + sb.append(prefix); sb.append("Ssp: \""); + sb.append(pe); sb.append("\""); + du.println(sb.toString()); + } + } if (mDataAuthorities != null) { Iterator it = mDataAuthorities.iterator(); while (it.hasNext()) { @@ -1363,6 +1526,15 @@ public class IntentFilter implements Parcelable { } else { dest.writeInt(0); } + if (mDataSchemeSpecificParts != null) { + final int N = mDataSchemeSpecificParts.size(); + dest.writeInt(N); + for (int i=0; i 0) { - mDataAuthorities = new ArrayList(); + mDataSchemeSpecificParts = new ArrayList(N); + for (int i=0; i 0) { + mDataAuthorities = new ArrayList(N); for (int i=0; i 0) { - mDataPaths = new ArrayList(); + mDataPaths = new ArrayList(N); for (int i=0; i CREATOR = new Creator() { + @Override public PeriodicSync createFromParcel(Parcel source) { - return new PeriodicSync(Account.CREATOR.createFromParcel(source), - source.readString(), source.readBundle(), source.readLong()); + return new PeriodicSync(source); } + @Override public PeriodicSync[] newArray(int size) { return new PeriodicSync[size]; } }; + @Override public boolean equals(Object o) { if (o == this) { return true; } - if (!(o instanceof PeriodicSync)) { return false; } - final PeriodicSync other = (PeriodicSync) o; - return account.equals(other.account) - && authority.equals(other.authority) - && period == other.period - && syncExtrasEquals(extras, other.extras); + && authority.equals(other.authority) + && period == other.period + && syncExtrasEquals(extras, other.extras); } - /** {@hide} */ + /** + * Periodic sync extra comparison function. + * {@hide} + */ public static boolean syncExtrasEquals(Bundle b1, Bundle b2) { if (b1.size() != b2.size()) { return false; @@ -100,4 +153,12 @@ public class PeriodicSync implements Parcelable { } return true; } + + @Override + public String toString() { + return "account: " + account + + ", authority: " + authority + + ". period: " + period + "s " + + ", flex: " + flexTime; + } } diff --git a/core/java/android/content/SyncRequest.aidl b/core/java/android/content/SyncRequest.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8321facb9cc2df7241e62b0d34358fa200e568a5 --- /dev/null +++ b/core/java/android/content/SyncRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable SyncRequest; diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..6ca283d8b819906f1a65716adf06b7deaddb8487 --- /dev/null +++ b/core/java/android/content/SyncRequest.java @@ -0,0 +1,625 @@ +/* + * 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.accounts.Account; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +public class SyncRequest implements Parcelable { + private static final String TAG = "SyncRequest"; + /** 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; + /** Sync service identifier. May be null.*/ + private final ComponentName mComponentInfo; + /** Bundle containing user info as well as sync settings. */ + private final Bundle mExtras; + /** 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. + */ + private final long mTxBytes; + /** + * Anticipated download size in bytes. + * TODO: Not yet used - we put this information into the bundle. + */ + private final long mRxBytes; + /** + * Amount of time before {@link #mSyncRunTimeSecs} from which the sync may optionally be + * started. + */ + private final long mSyncFlexTimeSecs; + /** + * Specifies a point in the future at which the sync must have been scheduled to run. + */ + private final long mSyncRunTimeSecs; + /** Periodic versus one-off. */ + private final boolean mIsPeriodic; + /** Service versus provider. */ + private final boolean mIsAuthority; + /** Sync should be run in lieu of other syncs. */ + private final boolean mIsExpedited; + + /** + * {@hide} + * @return whether this sync is periodic or one-time. A Sync Request must be + * either one of these or an InvalidStateException will be thrown in + * Builder.build(). + */ + public boolean isPeriodic() { + 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 sync is bound to + * a Sync Service. + */ + public boolean hasAuthority() { + return mIsAuthority; + } + + /** + * {@hide} + * @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 Account getAccount() { + if (!hasAuthority()) { + throw new IllegalArgumentException("Cannot getAccount() for a sync that does not" + + "specify an authority."); + } + return mAccountToSync; + } + + /** + * {@hide} + * @return provider for this sync. + * @throws IllegalArgumentException if this function is called for a request that does not + * specify an account/provider authority. + */ + public String getProvider() { + if (!hasAuthority()) { + throw new IllegalArgumentException("Cannot getProvider() for a sync that does not" + + "specify a provider."); + } + return mAuthority; + } + + /** + * {@hide} + * Retrieve bundle for this SyncRequest. Will not be null. + */ + public Bundle getBundle() { + return mExtras; + } + + /** + * {@hide} + * @return the earliest point in time that this sync can be scheduled. + */ + public long getSyncFlexTime() { + return mSyncFlexTimeSecs; + } + + /** + * {@hide} + * @return the last point in time at which this sync must scheduled. + */ + public long getSyncRunTime() { + return mSyncRunTimeSecs; + } + + public static final Creator CREATOR = new Creator() { + + @Override + public SyncRequest createFromParcel(Parcel in) { + return new SyncRequest(in); + } + + @Override + public SyncRequest[] newArray(int size) { + return new SyncRequest[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeBundle(mExtras); + parcel.writeLong(mSyncFlexTimeSecs); + parcel.writeLong(mSyncRunTimeSecs); + parcel.writeInt((mIsPeriodic ? 1 : 0)); + parcel.writeInt((mDisallowMetered ? 1 : 0)); + parcel.writeLong(mTxBytes); + parcel.writeLong(mRxBytes); + parcel.writeInt((mIsAuthority ? 1 : 0)); + parcel.writeInt((mIsExpedited? 1 : 0)); + if (mIsAuthority) { + parcel.writeParcelable(mAccountToSync, flags); + parcel.writeString(mAuthority); + } else { + parcel.writeParcelable(mComponentInfo, flags); + } + } + + private SyncRequest(Parcel in) { + mExtras = in.readBundle(); + mSyncFlexTimeSecs = in.readLong(); + mSyncRunTimeSecs = in.readLong(); + mIsPeriodic = (in.readInt() != 0); + mDisallowMetered = (in.readInt() != 0); + mTxBytes = in.readLong(); + mRxBytes = in.readLong(); + mIsAuthority = (in.readInt() != 0); + mIsExpedited = (in.readInt() != 0); + if (mIsAuthority) { + mComponentInfo = null; + mAccountToSync = in.readParcelable(null); + mAuthority = in.readString(); + } else { + mComponentInfo = in.readParcelable(null); + mAccountToSync = null; + mAuthority = null; + } + } + + /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */ + protected SyncRequest(SyncRequest.Builder b) { + mSyncFlexTimeSecs = b.mSyncFlexTimeSecs; + mSyncRunTimeSecs = b.mSyncRunTimeSecs; + mAccountToSync = b.mAccount; + mAuthority = b.mAuthority; + mComponentInfo = b.mComponentName; + mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC); + mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER); + mIsExpedited = b.mExpedited; + mExtras = new Bundle(b.mCustomExtras); + // 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); + mDisallowMetered = b.mDisallowMetered; + mTxBytes = b.mTxBytes; + mRxBytes = b.mRxBytes; + } + + /** + * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also + * perform validation. + */ + public static class Builder { + /** Unknown sync type. */ + private static final int SYNC_TYPE_UNKNOWN = 0; + /** Specify that this is a periodic sync. */ + private static final int SYNC_TYPE_PERIODIC = 1; + /** Specify that this is a one-time sync. */ + private static final int SYNC_TYPE_ONCE = 2; + /** Unknown sync target. */ + private static final int SYNC_TARGET_UNKNOWN = 0; + /** Specify that this is an anonymous sync. */ + 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. */ + private long mSyncFlexTimeSecs; + /** 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. + * This data is handed over to the sync operation. + */ + private Bundle mCustomExtras; + /** + * Sync system configuration - used to store system sync configuration. Corresponds to + * ContentResolver.SYNC_EXTRAS_* flags. + * TODO: Use this instead of dumping into one bundle. Need to decide if these flags should + * discriminate between equivalent syncs. + */ + private Bundle mSyncConfigExtras; + /** Expected upload transfer in bytes. */ + private long mTxBytes = -1L; + /** Expected download transfer in bytes. */ + private long mRxBytes = -1L; + /** Whether or not this sync can occur on metered networks. Default false. */ + private boolean mDisallowMetered; + /** Priority of this sync relative to others from calling app [-2, 2]. Default 0. */ + private int mPriority = 0; + /** + * Whether this builder is building a periodic sync, or a one-time sync. + */ + private int mSyncType = SYNC_TYPE_UNKNOWN; + /** Whether this will go to a sync adapter or to a sync service. */ + private int mSyncTarget = SYNC_TARGET_UNKNOWN; + /** Whether this is a user-activated sync. */ + private boolean mIsManual; + /** + * Whether to retry this one-time sync if the sync fails. Not valid for + * periodic syncs. See {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}. + */ + private boolean mNoRetry; + /** + * Whether to respect back-off for this one-time sync. Not valid for + * periodic syncs. See + * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}; + */ + private boolean mIgnoreBackoff; + + /** Ignore sync system settings and perform sync anyway. */ + private boolean mIgnoreSettings; + + /** This sync will run in preference to other non-expedited syncs. */ + private boolean mExpedited; + + /** + * The sync component that contains the sync logic if this is a provider-less sync, + * otherwise null. + */ + private ComponentName mComponentName; + /** + * The Account object that together with an Authority name define the SyncAdapter (if + * this sync is bound to a provider), otherwise null. + */ + private Account mAccount; + /** + * The Authority name that together with an Account define the SyncAdapter (if + * this sync is bound to a provider), otherwise null. + */ + private String mAuthority; + + public Builder() { + } + + /** + * Request that a sync occur immediately. + * + * Example + *

    +         *     SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce();
    +         * 
    + */ + public Builder syncOnce() { + if (mSyncType != SYNC_TYPE_UNKNOWN) { + throw new IllegalArgumentException("Sync type has already been defined."); + } + mSyncType = SYNC_TYPE_ONCE; + setupInterval(0, 0); + return this; + } + + /** + * Build a periodic sync. Either this or syncOnce() must be called for this builder. + * 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 IllegalArgumentException + * will be thrown. + * + * Example usage. + * + *
    +         *     Request a periodic sync every 5 hours with 20 minutes of flex.
    +         *     SyncRequest.Builder builder =
    +         *         (new SyncRequest.Builder()).syncPeriodic(5 * HOUR_IN_SECS, 20 * MIN_IN_SECS);
    +         *
    +         *     Schedule a periodic sync every hour at any point in time during that hour.
    +         *     SyncRequest.Builder builder =
    +         *         (new SyncRequest.Builder()).syncPeriodic(1 * HOUR_IN_SECS, 1 * HOUR_IN_SECS);
    +         * 
    + * + * N.B.: Periodic syncs are not allowed to have any of + * {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}, + * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}, + * {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}, + * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE}, + * {@link ContentResolver#SYNC_EXTRAS_FORCE}, + * {@link ContentResolver#SYNC_EXTRAS_EXPEDITED}, + * {@link ContentResolver#SYNC_EXTRAS_MANUAL} + * set to true. If any are supplied then an IllegalArgumentException will + * be thrown. + * + * @param pollFrequency the amount of time in seconds that you wish + * to elapse between periodic syncs. + * @param beforeSeconds the amount of flex time in seconds before + * {@code pollFrequency} that you permit for the sync to take + * place. Must be less than {@code pollFrequency}. + */ + public Builder syncPeriodic(long pollFrequency, long beforeSeconds) { + if (mSyncType != SYNC_TYPE_UNKNOWN) { + throw new IllegalArgumentException("Sync type has already been defined."); + } + mSyncType = SYNC_TYPE_PERIODIC; + setupInterval(pollFrequency, beforeSeconds); + return this; + } + + /** {@hide} */ + private void setupInterval(long at, long before) { + if (before > at) { + throw new IllegalArgumentException("Specified run time for the sync must be" + + " after the specified flex time."); + } + mSyncRunTimeSecs = at; + mSyncFlexTimeSecs = before; + } + + /** + * {@hide} + * Developer can provide insight into their payload size; optional. -1 specifies unknown, + * so that you are not restricted to defining both fields. + * + * @param rxBytes Bytes expected to be downloaded. + * @param txBytes Bytes expected to be uploaded. + */ + public Builder setTransferSize(long rxBytes, long txBytes) { + mRxBytes = rxBytes; + mTxBytes = txBytes; + return this; + } + + /** + * @see android.net.ConnectivityManager#isActiveNetworkMetered() + * @param disallow true to enforce that this transfer not occur on metered networks. + * Default false. + */ + public Builder setDisallowMetered(boolean disallow) { + mDisallowMetered = disallow; + return this; + } + + /** + * Specify an authority and account for this transfer. + * + * @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) { + throw new IllegalArgumentException("Sync target has already been defined."); + } + if (authority != null && authority.length() == 0) { + throw new IllegalArgumentException("Authority must be non-empty"); + } + mSyncTarget = SYNC_TARGET_ADAPTER; + mAccount = account; + mAuthority = authority; + mComponentName = null; + return this; + } + + /** + * 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: + *
    +         *   String[] syncItems = {"dog", "cat", "frog", "child"};
    +         *   SyncRequest.Builder builder =
    +         *     new SyncRequest.Builder()
    +         *       .setSyncAdapter(dummyAccount, dummyProvider)
    +         *       .syncOnce(5 * MINUTES_IN_SECS);
    +         *
    +         *   for (String syncData : syncItems) {
    +         *     Bundle extras = new Bundle();
    +         *     extras.setString("data", syncData);
    +         *     builder.setExtras(extras);
    +         *     ContentResolver.sync(builder.build()); // Each sync() request is for a unique sync.
    +         *   }
    +         * 
    + * Only values of the following types may be used in the extras bundle: + *
      + *
    • Integer
    • + *
    • Long
    • + *
    • Boolean
    • + *
    • Float
    • + *
    • Double
    • + *
    • String
    • + *
    • Account
    • + *
    • null
    • + *
    + * If any data is present in the bundle not of this type, build() will + * throw a runtime exception. + * + * @param bundle extras bundle to set. + */ + public Builder setExtras(Bundle bundle) { + mCustomExtras = bundle; + return this; + } + + /** + * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}. + * + * A one-off sync operation that fails will be retried with exponential back-off unless + * this is set to false. Not valid for periodic sync and will throw an + * IllegalArgumentException in build(). + * + * @param noRetry true to not retry a failed sync. Default false. + */ + public Builder setNoRetry(boolean noRetry) { + mNoRetry = noRetry; + return this; + } + + /** + * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}. + * + * A sync can specify that system sync settings be ignored (user has turned sync off). Not + * valid for periodic sync and will throw an IllegalArgumentException in + * {@link #build()}. + * + * @param ignoreSettings true to ignore the sync automatically settings. Default false. + */ + public Builder setIgnoreSettings(boolean ignoreSettings) { + mIgnoreSettings = ignoreSettings; + return this; + } + + /** + * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}. + * + * 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 IllegalArgumentException in + * {@link #build()}. + * + * @param ignoreBackoff ignore back-off settings. Default false. + */ + public Builder setIgnoreBackoff(boolean ignoreBackoff) { + mIgnoreBackoff = ignoreBackoff; + return this; + } + + /** + * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}. + * + * A manual sync is functionally equivalent to calling {@link #setIgnoreBackoff(boolean)} + * and {@link #setIgnoreSettings(boolean)}. Not valid for periodic sync and will throw an + * IllegalArgumentException in {@link #build()}. + * + * @param isManual User-initiated sync or not. Default false. + */ + public Builder setManual(boolean isManual) { + mIsManual = isManual; + return this; + } + + /** + * An expedited sync runs immediately and will preempt another non-expedited running sync. + * + * Not valid for periodic sync and will throw an IllegalArgumentException in + * {@link #build()}. + * + * @param expedited whether to run expedited. Default false. + */ + public Builder setExpedited(boolean expedited) { + mExpedited = expedited; + return this; + } + + /** + * {@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. + */ + public Builder setPriority(int priority) { + if (priority < -2 || priority > 2) { + throw new IllegalArgumentException("Priority must be within range [-2, 2]"); + } + mPriority = priority; + return this; + } + + /** + * Performs validation over the request and throws the runtime exception + * IllegalArgumentException if this validation fails. + * + * @return a SyncRequest with the information contained within this + * builder. + */ + public SyncRequest build() { + 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 (mDisallowMetered) { + mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true); + } + if (mIgnoreSettings) { + mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); + } + if (mNoRetry) { + mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true); + } + if (mExpedited) { + mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + } + if (mIsManual) { + mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + } + mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD, mTxBytes); + mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD, mRxBytes); + mSyncConfigExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority); + if (mSyncType == SYNC_TYPE_PERIODIC) { + // 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 " + + "setSyncAdapter(Account, String"); + } + return new SyncRequest(this); + } + + /** + * Helper function to throw an IllegalArgumentException if any illegal + * extras were set for a periodic sync. + * + * @param extras bundle to validate. + */ + private void validatePeriodicExtras(Bundle extras) { + if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { + throw new IllegalArgumentException("Illegal extras were set"); + } + } + } +} diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java index 8b0afbd6d666be269dc95c0c2ed1f732e2b1f083..4f86af985dc6359477b36d1b6e7602cc3798d2f2 100644 --- a/core/java/android/content/SyncResult.java +++ b/core/java/android/content/SyncResult.java @@ -56,7 +56,7 @@ public final class SyncResult implements Parcelable { /** * Used to indicate that the SyncAdapter experienced a hard error due to an error it - * received from interacting with the storage later. The SyncManager will record that + * received from interacting with the storage layer. The SyncManager will record that * the sync request failed and it will not reschedule the request. */ public boolean databaseError; @@ -181,7 +181,7 @@ public final class SyncResult implements Parcelable { *
  • {@link SyncStats#numIoExceptions} > 0 *
  • {@link #syncAlreadyInProgress} * - * @return true if a hard error is indicated + * @return true if a soft error is indicated */ public boolean hasSoftError() { return syncAlreadyInProgress || stats.numIoExceptions > 0; @@ -195,6 +195,11 @@ public final class SyncResult implements Parcelable { return hasSoftError() || hasHardError(); } + /** + * Convenience method for determining if the Sync should be rescheduled after failing for some + * reason. + * @return true if the SyncManager should reschedule this sync. + */ public boolean madeSomeProgress() { return ((stats.numDeletes > 0) && !tooManyDeletions) || stats.numInserts > 0 diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java index ff628d93f96be84d1b605ec40120a4323b746916..bb24ccd5a5edb01d468e8cecda31a5d2bbd1272b 100644 --- a/core/java/android/content/SyncStatusInfo.java +++ b/core/java/android/content/SyncStatusInfo.java @@ -42,7 +42,10 @@ public class SyncStatusInfo implements Parcelable { public long initialFailureTime; public boolean pending; public boolean initialize; - public ArrayList periodicSyncTimes; + + // Warning: It is up to the external caller to ensure there are + // no race conditions when accessing this list + private ArrayList periodicSyncTimes; private static final String TAG = "Sync"; @@ -126,34 +129,47 @@ public class SyncStatusInfo implements Parcelable { } } + public SyncStatusInfo(SyncStatusInfo other) { + authorityId = other.authorityId; + totalElapsedTime = other.totalElapsedTime; + numSyncs = other.numSyncs; + numSourcePoll = other.numSourcePoll; + numSourceServer = other.numSourceServer; + numSourceLocal = other.numSourceLocal; + numSourceUser = other.numSourceUser; + numSourcePeriodic = other.numSourcePeriodic; + lastSuccessTime = other.lastSuccessTime; + lastSuccessSource = other.lastSuccessSource; + lastFailureTime = other.lastFailureTime; + lastFailureSource = other.lastFailureSource; + lastFailureMesg = other.lastFailureMesg; + initialFailureTime = other.initialFailureTime; + pending = other.pending; + initialize = other.initialize; + if (other.periodicSyncTimes != null) { + periodicSyncTimes = new ArrayList(other.periodicSyncTimes); + } + } + public void setPeriodicSyncTime(int index, long when) { + // The list is initialized lazily when scheduling occurs so we need to make sure + // we initialize elements < index to zero (zero is ignore for scheduling purposes) ensurePeriodicSyncTimeSize(index); periodicSyncTimes.set(index, when); } - private void ensurePeriodicSyncTimeSize(int index) { - if (periodicSyncTimes == null) { - periodicSyncTimes = new ArrayList(0); - } - - final int requiredSize = index + 1; - if (periodicSyncTimes.size() < requiredSize) { - for (int i = periodicSyncTimes.size(); i < requiredSize; i++) { - periodicSyncTimes.add((long) 0); - } - } - } - public long getPeriodicSyncTime(int index) { - if (periodicSyncTimes == null || periodicSyncTimes.size() < (index + 1)) { + if (periodicSyncTimes != null && index < periodicSyncTimes.size()) { + return periodicSyncTimes.get(index); + } else { return 0; } - return periodicSyncTimes.get(index); } public void removePeriodicSyncTime(int index) { - ensurePeriodicSyncTimeSize(index); - periodicSyncTimes.remove(index); + if (periodicSyncTimes != null && index < periodicSyncTimes.size()) { + periodicSyncTimes.remove(index); + } } public static final Creator CREATOR = new Creator() { @@ -165,4 +181,17 @@ public class SyncStatusInfo implements Parcelable { return new SyncStatusInfo[size]; } }; + + private void ensurePeriodicSyncTimeSize(int index) { + if (periodicSyncTimes == null) { + periodicSyncTimes = new ArrayList(0); + } + + final int requiredSize = index + 1; + if (periodicSyncTimes.size() < requiredSize) { + for (int i = periodicSyncTimes.size(); i < requiredSize; i++) { + periodicSyncTimes.add((long) 0); + } + } + } } \ No newline at end of file diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java new file mode 100644 index 0000000000000000000000000000000000000000..e9ec5a4a982b7c04975871928f27ae283cb8a4a1 --- /dev/null +++ b/core/java/android/content/UndoManager.java @@ -0,0 +1,934 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelableParcel; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Top-level class for managing and interacting with the global undo state for + * a document or application. This class supports both undo and redo and has + * helpers for merging undoable operations together as they are performed. + * + *

    A single undoable operation is represented by {@link UndoOperation} which + * apps implement to define their undo/redo behavior. The UndoManager keeps + * a stack of undo states; each state can have one or more undo operations + * inside of it.

    + * + *

    Updates to the stack must be done inside of a {@link #beginUpdate}/{@link #endUpdate()} + * pair. During this time you can add new operations to the stack with + * {@link #addOperation}, retrieve and modify existing operations with + * {@link #getLastOperation}, control the label shown to the user for this operation + * with {@link #setUndoLabel} and {@link #suggestUndoLabel}, etc.

    + * + *

    Every {link UndoOperation} is associated with an {@link UndoOwner}, which identifies + * the data it belongs to. The owner is used to indicate how operations are dependent + * on each other -- operations with the same owner are dependent on others with the + * same owner. For example, you may have a document with multiple embedded objects. If the + * document itself and each embedded object use different owners, then you + * can provide undo semantics appropriate to the user's context: while within + * an embedded object, only edits to that object are seen and the user can + * undo/redo them without needing to impact edits in other objects; while + * within the larger document, all edits can be seen and the user must + * undo/redo them as a single stream.

    + * + * @hide + */ +public class UndoManager { + private final HashMap mOwners = new HashMap(); + private final ArrayList mUndos = new ArrayList(); + private final ArrayList mRedos = new ArrayList(); + private int mUpdateCount; + private int mHistorySize = 20; + private UndoState mWorking; + private int mCommitId = 1; + private boolean mInUndo; + private boolean mMerged; + + private int mStateSeq; + private int mNextSavedIdx; + private UndoOwner[] mStateOwners; + + /** + * Never merge with the last undo state. + */ + public static final int MERGE_MODE_NONE = 0; + + /** + * Allow merge with the last undo state only if it contains + * operations with the caller's owner. + */ + public static final int MERGE_MODE_UNIQUE = 1; + + /** + * Always allow merge with the last undo state, if possible. + */ + public static final int MERGE_MODE_ANY = 2; + + public UndoOwner getOwner(String tag, Object data) { + if (tag == null) { + throw new NullPointerException("tag can't be null"); + } + if (data == null) { + throw new NullPointerException("data can't be null"); + } + UndoOwner owner = mOwners.get(tag); + if (owner != null) { + if (owner.mData != data) { + if (owner.mData != null) { + throw new IllegalStateException("Owner " + owner + " already exists with data " + + owner.mData + " but giving different data " + data); + } + owner.mData = data; + } + return owner; + } + + owner = new UndoOwner(tag); + owner.mManager = this; + owner.mData = data; + mOwners.put(tag, owner); + return owner; + } + + void removeOwner(UndoOwner owner) { + // XXX need to figure out how to prune. + if (false) { + mOwners.remove(owner.mTag); + owner.mManager = null; + } + } + + /** + * Flatten the current undo state into a Parcelable object, which can later be restored + * with {@link #restoreInstanceState(android.os.Parcelable)}. + */ + public Parcelable saveInstanceState() { + if (mUpdateCount > 0) { + throw new IllegalStateException("Can't save state while updating"); + } + ParcelableParcel pp = new ParcelableParcel(getClass().getClassLoader()); + Parcel p = pp.getParcel(); + mStateSeq++; + if (mStateSeq <= 0) { + mStateSeq = 0; + } + mNextSavedIdx = 0; + p.writeInt(mHistorySize); + p.writeInt(mOwners.size()); + // XXX eventually we need to be smart here about limiting the + // number of undo states we write to not exceed X bytes. + int i = mUndos.size(); + while (i > 0) { + p.writeInt(1); + i--; + mUndos.get(i).writeToParcel(p); + } + i = mRedos.size(); + p.writeInt(i); + while (i > 0) { + p.writeInt(2); + i--; + mRedos.get(i).writeToParcel(p); + } + p.writeInt(0); + return pp; + } + + void saveOwner(UndoOwner owner, Parcel out) { + if (owner.mStateSeq == mStateSeq) { + out.writeInt(owner.mSavedIdx); + } else { + owner.mStateSeq = mStateSeq; + owner.mSavedIdx = mNextSavedIdx; + out.writeInt(owner.mSavedIdx); + out.writeString(owner.mTag); + mNextSavedIdx++; + } + } + + /** + * Restore an undo state previously created with {@link #saveInstanceState()}. This will + * restore the UndoManager's state to almost exactly what it was at the point it had + * been previously saved; the only information not restored is the data object + * associated with each {@link UndoOwner}, which requires separate calls to + * {@link #getOwner(String, Object)} to re-associate the owner with its data. + */ + public void restoreInstanceState(Parcelable state) { + if (mUpdateCount > 0) { + throw new IllegalStateException("Can't save state while updating"); + } + forgetUndos(null, -1); + forgetRedos(null, -1); + ParcelableParcel pp = (ParcelableParcel)state; + Parcel p = pp.getParcel(); + mHistorySize = p.readInt(); + mStateOwners = new UndoOwner[p.readInt()]; + + int stype; + while ((stype=p.readInt()) != 0) { + UndoState ustate = new UndoState(this, p, pp.getClassLoader()); + if (stype == 1) { + mUndos.add(0, ustate); + } else { + mRedos.add(0, ustate); + } + } + } + + UndoOwner restoreOwner(Parcel in) { + int idx = in.readInt(); + UndoOwner owner = mStateOwners[idx]; + if (owner == null) { + String tag = in.readString(); + owner = new UndoOwner(tag); + mStateOwners[idx] = owner; + mOwners.put(tag, owner); + } + return owner; + } + + /** + * Set the maximum number of undo states that will be retained. + */ + public void setHistorySize(int size) { + mHistorySize = size; + if (mHistorySize >= 0 && countUndos(null) > mHistorySize) { + forgetUndos(null, countUndos(null) - mHistorySize); + } + } + + /** + * Return the current maximum number of undo states. + */ + public int getHistorySize() { + return mHistorySize; + } + + /** + * Perform undo of last/top count undo states. The states impacted + * by this can be limited through owners. + * @param owners Optional set of owners that should be impacted. If null, all + * undo states will be visible and available for undo. If non-null, only those + * states that contain one of the owners specified here will be visible. + * @param count Number of undo states to pop. + * @return Returns the number of undo states that were actually popped. + */ + public int undo(UndoOwner[] owners, int count) { + if (mWorking != null) { + throw new IllegalStateException("Can't be called during an update"); + } + + int num = 0; + int i = -1; + + mInUndo = true; + + UndoState us = getTopUndo(null); + if (us != null) { + us.makeExecuted(); + } + + while (count > 0 && (i=findPrevState(mUndos, owners, i)) >= 0) { + UndoState state = mUndos.remove(i); + state.undo(); + mRedos.add(state); + count--; + num++; + } + + mInUndo = false; + + return num; + } + + /** + * Perform redo of last/top count undo states in the transient redo stack. + * The states impacted by this can be limited through owners. + * @param owners Optional set of owners that should be impacted. If null, all + * undo states will be visible and available for undo. If non-null, only those + * states that contain one of the owners specified here will be visible. + * @param count Number of undo states to pop. + * @return Returns the number of undo states that were actually redone. + */ + public int redo(UndoOwner[] owners, int count) { + if (mWorking != null) { + throw new IllegalStateException("Can't be called during an update"); + } + + int num = 0; + int i = -1; + + mInUndo = true; + + while (count > 0 && (i=findPrevState(mRedos, owners, i)) >= 0) { + UndoState state = mRedos.remove(i); + state.redo(); + mUndos.add(state); + count--; + num++; + } + + mInUndo = false; + + return num; + } + + /** + * Returns true if we are currently inside of an undo/redo operation. This is + * useful for editors to know whether they should be generating new undo state + * when they see edit operations happening. + */ + public boolean isInUndo() { + return mInUndo; + } + + public int forgetUndos(UndoOwner[] owners, int count) { + if (count < 0) { + count = mUndos.size(); + } + + int removed = 0; + for (int i=0; i 0 && matchOwners(state, owners)) { + state.destroy(); + mUndos.remove(i); + removed++; + } + } + + return removed; + } + + public int forgetRedos(UndoOwner[] owners, int count) { + if (count < 0) { + count = mRedos.size(); + } + + int removed = 0; + for (int i=0; i 0 && matchOwners(state, owners)) { + state.destroy(); + mRedos.remove(i); + removed++; + } + } + + return removed; + } + + /** + * Return the number of undo states on the undo stack. + * @param owners If non-null, only those states containing an operation with one of + * the owners supplied here will be counted. + */ + public int countUndos(UndoOwner[] owners) { + if (owners == null) { + return mUndos.size(); + } + + int count=0; + int i=0; + while ((i=findNextState(mUndos, owners, i)) >= 0) { + count++; + i++; + } + return count; + } + + /** + * Return the number of redo states on the undo stack. + * @param owners If non-null, only those states containing an operation with one of + * the owners supplied here will be counted. + */ + public int countRedos(UndoOwner[] owners) { + if (owners == null) { + return mRedos.size(); + } + + int count=0; + int i=0; + while ((i=findNextState(mRedos, owners, i)) >= 0) { + count++; + i++; + } + return count; + } + + /** + * Return the user-visible label for the top undo state on the stack. + * @param owners If non-null, will select the top-most undo state containing an + * operation with one of the owners supplied here. + */ + public CharSequence getUndoLabel(UndoOwner[] owners) { + UndoState state = getTopUndo(owners); + return state != null ? state.getLabel() : null; + } + + /** + * Return the user-visible label for the top redo state on the stack. + * @param owners If non-null, will select the top-most undo state containing an + * operation with one of the owners supplied here. + */ + public CharSequence getRedoLabel(UndoOwner[] owners) { + UndoState state = getTopRedo(owners); + return state != null ? state.getLabel() : null; + } + + /** + * Start creating a new undo state. Multiple calls to this function will nest until + * they are all matched by a later call to {@link #endUpdate}. + * @param label Optional user-visible label for this new undo state. + */ + public void beginUpdate(CharSequence label) { + if (mInUndo) { + throw new IllegalStateException("Can't being update while performing undo/redo"); + } + if (mUpdateCount <= 0) { + createWorkingState(); + mMerged = false; + mUpdateCount = 0; + } + + mWorking.updateLabel(label); + mUpdateCount++; + } + + private void createWorkingState() { + mWorking = new UndoState(this, mCommitId++); + if (mCommitId < 0) { + mCommitId = 1; + } + } + + /** + * Returns true if currently inside of a {@link #beginUpdate}. + */ + public boolean isInUpdate() { + return mUpdateCount > 0; + } + + /** + * Forcibly set a new for the new undo state being built within a {@link #beginUpdate}. + * Any existing label will be replaced with this one. + */ + public void setUndoLabel(CharSequence label) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + mWorking.setLabel(label); + } + + /** + * Set a new for the new undo state being built within a {@link #beginUpdate}, but + * only if there is not a label currently set for it. + */ + public void suggestUndoLabel(CharSequence label) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + mWorking.updateLabel(label); + } + + /** + * Return the number of times {@link #beginUpdate} has been called without a matching + * {@link #endUpdate} call. + */ + public int getUpdateNestingLevel() { + return mUpdateCount; + } + + /** + * Check whether there is an {@link UndoOperation} in the current {@link #beginUpdate} + * undo state. + * @param owner Optional owner of the operation to look for. If null, will succeed + * if there is any operation; if non-null, will only succeed if there is an operation + * with the given owner. + * @return Returns true if there is a matching operation in the current undo state. + */ + public boolean hasOperation(UndoOwner owner) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + return mWorking.hasOperation(owner); + } + + /** + * Return the most recent {@link UndoOperation} that was added to the update. + * @param mergeMode May be either {@link #MERGE_MODE_NONE} or {@link #MERGE_MODE_ANY}. + */ + public UndoOperation getLastOperation(int mergeMode) { + return getLastOperation(null, null, mergeMode); + } + + /** + * Return the most recent {@link UndoOperation} that was added to the update and + * has the given owner. + * @param owner Optional owner of last operation to retrieve. If null, the last + * operation regardless of owner will be retrieved; if non-null, the last operation + * matching the given owner will be retrieved. + * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, + * or {@link #MERGE_MODE_ANY}. + */ + public UndoOperation getLastOperation(UndoOwner owner, int mergeMode) { + return getLastOperation(null, owner, mergeMode); + } + + /** + * Return the most recent {@link UndoOperation} that was added to the update and + * has the given owner. + * @param clazz Optional class of the last operation to retrieve. If null, the + * last operation regardless of class will be retrieved; if non-null, the last + * operation whose class is the same as the given class will be retrieved. + * @param owner Optional owner of last operation to retrieve. If null, the last + * operation regardless of owner will be retrieved; if non-null, the last operation + * matching the given owner will be retrieved. + * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, + * or {@link #MERGE_MODE_ANY}. + */ + public T getLastOperation(Class clazz, UndoOwner owner, + int mergeMode) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) { + UndoState state = getTopUndo(null); + UndoOperation last; + if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners()) + && state.canMerge() && (last=state.getLastOperation(clazz, owner)) != null) { + if (last.allowMerge()) { + mWorking.destroy(); + mWorking = state; + mUndos.remove(state); + mMerged = true; + return (T)last; + } + } + } + + return mWorking.getLastOperation(clazz, owner); + } + + /** + * Add a new UndoOperation to the current update. + * @param op The new operation to add. + * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, + * or {@link #MERGE_MODE_ANY}. + */ + public void addOperation(UndoOperation op, int mergeMode) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + UndoOwner owner = op.getOwner(); + if (owner.mManager != this) { + throw new IllegalArgumentException( + "Given operation's owner is not in this undo manager."); + } + if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) { + UndoState state = getTopUndo(null); + if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners()) + && state.canMerge() && state.hasOperation(op.getOwner())) { + mWorking.destroy(); + mWorking = state; + mUndos.remove(state); + mMerged = true; + } + } + mWorking.addOperation(op); + } + + /** + * Finish the creation of an undo state, matching a previous call to + * {@link #beginUpdate}. + */ + public void endUpdate() { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + mUpdateCount--; + + if (mUpdateCount == 0) { + pushWorkingState(); + } + } + + private void pushWorkingState() { + int N = mUndos.size() + 1; + + if (mWorking.hasData()) { + mUndos.add(mWorking); + forgetRedos(null, -1); + mWorking.commit(); + if (N >= 2) { + // The state before this one can no longer be merged, ever. + // The only way to get back to it is for the user to perform + // an undo. + mUndos.get(N-2).makeExecuted(); + } + } else { + mWorking.destroy(); + } + mWorking = null; + + if (mHistorySize >= 0 && N > mHistorySize) { + forgetUndos(null, N - mHistorySize); + } + } + + /** + * Commit the last finished undo state. This undo state can no longer be + * modified with further {@link #MERGE_MODE_UNIQUE} or + * {@link #MERGE_MODE_ANY} merge modes. If called while inside of an update, + * this will push any changes in the current update on to the undo stack + * and result with a fresh undo state, behaving as if {@link #endUpdate()} + * had been called enough to unwind the current update, then the last state + * committed, and {@link #beginUpdate} called to restore the update nesting. + * @param owner The optional owner to determine whether to perform the commit. + * If this is non-null, the commit will only execute if the current top undo + * state contains an operation with the given owner. + * @return Returns an integer identifier for the committed undo state, which + * can later be used to try to uncommit the state to perform further edits on it. + */ + public int commitState(UndoOwner owner) { + if (mWorking != null && mWorking.hasData()) { + if (owner == null || mWorking.hasOperation(owner)) { + mWorking.setCanMerge(false); + int commitId = mWorking.getCommitId(); + pushWorkingState(); + createWorkingState(); + mMerged = true; + return commitId; + } + } else { + UndoState state = getTopUndo(null); + if (state != null && (owner == null || state.hasOperation(owner))) { + state.setCanMerge(false); + return state.getCommitId(); + } + } + return -1; + } + + /** + * Attempt to undo a previous call to {@link #commitState}. This will work + * if the undo state at the top of the stack has the given id, and has not been + * involved in an undo operation. Otherwise false is returned. + * @param commitId The identifier for the state to be uncommitted, as returned + * by {@link #commitState}. + * @param owner Optional owner that must appear in the committed state. + * @return Returns true if the uncommit is successful, else false. + */ + public boolean uncommitState(int commitId, UndoOwner owner) { + if (mWorking != null && mWorking.getCommitId() == commitId) { + if (owner == null || mWorking.hasOperation(owner)) { + return mWorking.setCanMerge(true); + } + } else { + UndoState state = getTopUndo(null); + if (state != null && (owner == null || state.hasOperation(owner))) { + if (state.getCommitId() == commitId) { + return state.setCanMerge(true); + } + } + } + return false; + } + + UndoState getTopUndo(UndoOwner[] owners) { + if (mUndos.size() <= 0) { + return null; + } + int i = findPrevState(mUndos, owners, -1); + return i >= 0 ? mUndos.get(i) : null; + } + + UndoState getTopRedo(UndoOwner[] owners) { + if (mRedos.size() <= 0) { + return null; + } + int i = findPrevState(mRedos, owners, -1); + return i >= 0 ? mRedos.get(i) : null; + } + + boolean matchOwners(UndoState state, UndoOwner[] owners) { + if (owners == null) { + return true; + } + for (int i=0; i states, UndoOwner[] owners, int from) { + final int N = states.size(); + + if (from == -1) { + from = N-1; + } + if (from >= N) { + return -1; + } + if (owners == null) { + return from; + } + + while (from >= 0) { + UndoState state = states.get(from); + if (matchOwners(state, owners)) { + return from; + } + from--; + } + + return -1; + } + + int findNextState(ArrayList states, UndoOwner[] owners, int from) { + final int N = states.size(); + + if (from < 0) { + from = 0; + } + if (from >= N) { + return -1; + } + if (owners == null) { + return from; + } + + while (from < N) { + UndoState state = states.get(from); + if (matchOwners(state, owners)) { + return from; + } + from++; + } + + return -1; + } + + final static class UndoState { + private final UndoManager mManager; + private final int mCommitId; + private final ArrayList> mOperations = new ArrayList>(); + private ArrayList> mRecent; + private CharSequence mLabel; + private boolean mCanMerge = true; + private boolean mExecuted; + + UndoState(UndoManager manager, int commitId) { + mManager = manager; + mCommitId = commitId; + } + + UndoState(UndoManager manager, Parcel p, ClassLoader loader) { + mManager = manager; + mCommitId = p.readInt(); + mCanMerge = p.readInt() != 0; + mExecuted = p.readInt() != 0; + mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); + final int N = p.readInt(); + for (int i=0; i op) { + if (mOperations.contains(op)) { + throw new IllegalStateException("Already holds " + op); + } + mOperations.add(op); + if (mRecent == null) { + mRecent = new ArrayList>(); + mRecent.add(op); + } + op.mOwner.mOpCount++; + } + + T getLastOperation(Class clazz, UndoOwner owner) { + final int N = mOperations.size(); + if (clazz == null && owner == null) { + return N > 0 ? (T)mOperations.get(N-1) : null; + } + // First look for the top-most operation with the same owner. + for (int i=N-1; i>=0; i--) { + UndoOperation op = mOperations.get(i); + if (owner != null && op.getOwner() != owner) { + continue; + } + // Return this operation if it has the same class that the caller wants. + // Note that we don't search deeper for the class, because we don't want + // to end up with a different order of operations for the same owner. + if (clazz != null && op.getClass() != clazz) { + return null; + } + return (T)op; + } + + return null; + } + + boolean matchOwner(UndoOwner owner) { + for (int i=mOperations.size()-1; i>=0; i--) { + if (mOperations.get(i).matchOwner(owner)) { + return true; + } + } + return false; + } + + boolean hasData() { + for (int i=mOperations.size()-1; i>=0; i--) { + if (mOperations.get(i).hasData()) { + return true; + } + } + return false; + } + + void commit() { + final int N = mRecent != null ? mRecent.size() : 0; + for (int i=0; i=0; i--) { + mOperations.get(i).undo(); + } + } + + void redo() { + final int N = mOperations.size(); + for (int i=0; i=0; i--) { + UndoOwner owner = mOperations.get(i).mOwner; + owner.mOpCount--; + if (owner.mOpCount <= 0) { + if (owner.mOpCount < 0) { + throw new IllegalStateException("Underflow of op count on owner " + owner + + " in op " + mOperations.get(i)); + } + mManager.removeOwner(owner); + } + } + } + } +} diff --git a/core/java/android/content/UndoOperation.java b/core/java/android/content/UndoOperation.java new file mode 100644 index 0000000000000000000000000000000000000000..1ff32d4a80139dcbf6d3cb00c198e0a34ef0b863 --- /dev/null +++ b/core/java/android/content/UndoOperation.java @@ -0,0 +1,112 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; + +/** + * A single undoable operation. You must subclass this to implement the state + * and behavior for your operation. Instances of this class are placed and + * managed in an {@link UndoManager}. + * + * @hide + */ +public abstract class UndoOperation implements Parcelable { + UndoOwner mOwner; + + /** + * Create a new instance of the operation. + * @param owner Who owns the data being modified by this undo state; must be + * returned by {@link UndoManager#getOwner(String, Object) UndoManager.getOwner}. + */ + public UndoOperation(UndoOwner owner) { + mOwner = owner; + } + + /** + * Construct from a Parcel. + */ + protected UndoOperation(Parcel src, ClassLoader loader) { + } + + /** + * Owning object as given to {@link #UndoOperation(UndoOwner)}. + */ + public UndoOwner getOwner() { + return mOwner; + } + + /** + * Synonym for {@link #getOwner()}.{@link android.content.UndoOwner#getData()}. + */ + public DATA getOwnerData() { + return (DATA)mOwner.getData(); + } + + /** + * Return true if this undo operation is a member of the given owner. + * The default implementation is owner == getOwner(). You + * can override this to provide more sophisticated dependencies between + * owners. + */ + public boolean matchOwner(UndoOwner owner) { + return owner == getOwner(); + } + + /** + * Return true if this operation actually contains modification data. The + * default implementation always returns true. If you return false, the + * operation will be dropped when the final undo state is being built. + */ + public boolean hasData() { + return true; + } + + /** + * Return true if this operation can be merged with a later operation. + * The default implementation always returns true. + */ + public boolean allowMerge() { + return true; + } + + /** + * Called when this undo state is being committed to the undo stack. + * The implementation should perform the initial edits and save any state that + * may be needed to undo them. + */ + public abstract void commit(); + + /** + * Called when this undo state is being popped off the undo stack (in to + * the temporary redo stack). The implementation should remove the original + * edits and thus restore the target object to its prior value. + */ + public abstract void undo(); + + /** + * Called when this undo state is being pushed back from the transient + * redo stack to the main undo stack. The implementation should re-apply + * the edits that were previously removed by {@link #undo}. + */ + public abstract void redo(); + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/content/UndoOwner.java b/core/java/android/content/UndoOwner.java new file mode 100644 index 0000000000000000000000000000000000000000..d0cdc950adae57556c882359b2c9f22e0dfa6de5 --- /dev/null +++ b/core/java/android/content/UndoOwner.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * Representation of an owner of {@link UndoOperation} objects in an {@link UndoManager}. + * + * @hide + */ +public class UndoOwner { + final String mTag; + + UndoManager mManager; + Object mData; + int mOpCount; + + // For saving/restoring state. + int mStateSeq; + int mSavedIdx; + + UndoOwner(String tag) { + mTag = tag; + } + + /** + * Return the unique tag name identifying this owner. This is the tag + * supplied to {@link UndoManager#getOwner(String, Object) UndoManager.getOwner} + * and is immutable. + */ + public String getTag() { + return mTag; + } + + /** + * Return the actual data object of the owner. This is the data object + * supplied to {@link UndoManager#getOwner(String, Object) UndoManager.getOwner}. An + * owner may have a null data if it was restored from a previously saved state with + * no getOwner call to associate it with its data. + */ + public Object getData() { + return mData; + } +} diff --git a/core/java/android/content/UriPermission.java b/core/java/android/content/UriPermission.java new file mode 100644 index 0000000000000000000000000000000000000000..df9200d7084c52280d6c536a4f76dff0a2cc61fc --- /dev/null +++ b/core/java/android/content/UriPermission.java @@ -0,0 +1,117 @@ +/* + * 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.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Description of a single Uri permission grant. This grants may have been + * created via {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, etc when sending + * an {@link Intent}, or explicitly through + * {@link Context#grantUriPermission(String, android.net.Uri, int)}. + * + * @see ContentResolver#getPersistedUriPermissions() + */ +public final class UriPermission implements Parcelable { + private final Uri mUri; + private final int mModeFlags; + private final long mPersistedTime; + + /** + * Value returned when a permission has not been persisted. + */ + public static final long INVALID_TIME = Long.MIN_VALUE; + + /** {@hide} */ + public UriPermission(Uri uri, int modeFlags, long persistedTime) { + mUri = uri; + mModeFlags = modeFlags; + mPersistedTime = persistedTime; + } + + /** {@hide} */ + public UriPermission(Parcel in) { + mUri = in.readParcelable(null); + mModeFlags = in.readInt(); + mPersistedTime = in.readLong(); + } + + /** + * Return the Uri this permission pertains to. + */ + public Uri getUri() { + return mUri; + } + + /** + * Returns if this permission offers read access. + */ + public boolean isReadPermission() { + return (mModeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0; + } + + /** + * Returns if this permission offers write access. + */ + public boolean isWritePermission() { + return (mModeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0; + } + + /** + * Return the time when this permission was first persisted, in milliseconds + * since January 1, 1970 00:00:00.0 UTC. Returns {@link #INVALID_TIME} if + * not persisted. + * + * @see ContentResolver#takePersistableUriPermission(Uri, int) + * @see System#currentTimeMillis() + */ + public long getPersistedTime() { + return mPersistedTime; + } + + @Override + public String toString() { + return "UriPermission {uri=" + mUri + ", modeFlags=" + mModeFlags + ", persistedTime=" + + mPersistedTime + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mUri, flags); + dest.writeInt(mModeFlags); + dest.writeLong(mPersistedTime); + } + + public static final Creator CREATOR = new Creator() { + @Override + public UriPermission createFromParcel(Parcel source) { + return new UriPermission(source); + } + + @Override + public UriPermission[] newArray(int size) { + return new UriPermission[size]; + } + }; +} diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 8154bca2cb89ca75e401b5898b2083524cf3d89c..b8ac3bf9a8e00fc8a19764faa8b45b9fdaee4ec4 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.res.Configuration; import android.os.Parcel; import android.os.Parcelable; import android.util.Printer; @@ -437,20 +438,20 @@ public class ActivityInfo extends ComponentInfo * native side given the bit we have assigned in ActivityInfo. */ public static int[] CONFIG_NATIVE_BITS = new int[] { - 0x0001, // MNC - 0x0002, // MCC - 0x0004, // LOCALE - 0x0008, // TOUCH SCREEN - 0x0010, // KEYBOARD - 0x0020, // KEYBOARD HIDDEN - 0x0040, // NAVIGATION - 0x0080, // ORIENTATION - 0x0800, // SCREEN LAYOUT - 0x1000, // UI MODE - 0x0200, // SCREEN SIZE - 0x2000, // SMALLEST SCREEN SIZE - 0x0100, // DENSITY - 0x4000, // LAYOUT DIRECTION + Configuration.NATIVE_CONFIG_MNC, // MNC + Configuration.NATIVE_CONFIG_MCC, // MCC + Configuration.NATIVE_CONFIG_LOCALE, // LOCALE + Configuration.NATIVE_CONFIG_TOUCHSCREEN, // TOUCH SCREEN + Configuration.NATIVE_CONFIG_KEYBOARD, // KEYBOARD + Configuration.NATIVE_CONFIG_KEYBOARD_HIDDEN, // KEYBOARD HIDDEN + Configuration.NATIVE_CONFIG_NAVIGATION, // NAVIGATION + Configuration.NATIVE_CONFIG_ORIENTATION, // ORIENTATION + Configuration.NATIVE_CONFIG_SCREEN_LAYOUT, // SCREEN LAYOUT + Configuration.NATIVE_CONFIG_UI_MODE, // UI MODE + Configuration.NATIVE_CONFIG_SCREEN_SIZE, // SCREEN SIZE + Configuration.NATIVE_CONFIG_SMALLEST_SCREEN_SIZE, // SMALLEST SCREEN SIZE + Configuration.NATIVE_CONFIG_DENSITY, // DENSITY + Configuration.NATIVE_CONFIG_LAYOUTDIR, // LAYOUT DIRECTION }; /** @hide * Convert Java change bits to native. diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 02401dc5416badf774d6b00fee78ea815497e61f..9c46d967f1a89e32ae6a21ef90e4c0e1dfc1df85 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -314,6 +314,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int FLAG_IS_DATA_ONLY = 1<<24; + /** + * Value for {@link #flags}: set to {@code true} if the application + * is permitted to hold privileged permissions. + * + * {@hide} + */ + public static final int FLAG_PRIVILEGED = 1<<30; + /** * Value for {@link #flags}: Set to true if the application has been * installed using the forward lock option. @@ -337,6 +345,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int FLAG_CANT_SAVE_STATE = 1<<28; + /** + * Value for {@link #flags}: true if the application is blocked via restrictions and for + * most purposes is considered as not installed. + * {@hide} + */ + public static final int FLAG_BLOCKED = 1<<27; + /** * Flags associated with the application. Any combination of * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE}, @@ -351,7 +366,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_INSTALLED}. */ public int flags = 0; - + /** * The required smallest screen width the application can run on. If 0, * nothing has been specified. Comes from diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java index 28124775fb9556a90c3206a13a039b1ecdf44fb4..4dbcf233dd33df9960856672bde139fa6f15fe81 100644 --- a/core/java/android/content/pm/ComponentInfo.java +++ b/core/java/android/content/pm/ComponentInfo.java @@ -116,6 +116,17 @@ public class ComponentInfo extends PackageItemInfo { public final int getIconResource() { return icon != 0 ? icon : applicationInfo.icon; } + + /** + * Return the logo resource identifier to use for this component. If + * the component defines a logo, that is used; else, the application + * logo is used. + * + * @return The logo associated with this component. + */ + public final int getLogoResource() { + return logo != 0 ? logo : applicationInfo.logo; + } protected void dumpFront(Printer pw, String prefix) { super.dumpFront(pw, prefix); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index a0e15551a78fa67ec832f5eb454fb8551d66086a..267fb2af2dd16c276b1baa2f696d8a8b81626c49 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -101,7 +101,9 @@ interface IPackageManager { String getNameForUid(int uid); int getUidForSharedUser(String sharedUserName); - + + int getFlagsForUid(int uid); + ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId); List queryIntentActivities(in Intent intent, @@ -121,6 +123,9 @@ interface IPackageManager { List queryIntentServices(in Intent intent, String resolvedType, int flags, int userId); + List queryIntentContentProviders(in Intent intent, + String resolvedType, int flags, int userId); + /** * This implements getInstalledPackages via a "last returned row" * mechanism that is not exposed in the API. This is to get around the IPC @@ -214,6 +219,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); @@ -224,7 +235,13 @@ interface IPackageManager { int getPreferredActivities(out List outFilters, out List outActivities, String packageName); - + + /** + * Report the set of 'Home' activity candidates, plus (if any) which of them + * is the current "always use this one" setting. + */ + ComponentName getHomeActivities(out List outHomeCandidates); + /** * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}. */ @@ -399,4 +416,7 @@ interface IPackageManager { /** Reflects current DeviceStorageMonitorService state */ boolean isStorageLow(); + + boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked, int userId); + boolean getApplicationBlockedSettingAsUser(String packageName, int userId); } diff --git a/core/java/android/content/pm/KeySet.java b/core/java/android/content/pm/KeySet.java new file mode 100644 index 0000000000000000000000000000000000000000..0ef09a432528c959c4bc976318a7f28bbb156d8a --- /dev/null +++ b/core/java/android/content/pm/KeySet.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.os.Binder; + +/** @hide */ +public class KeySet { + + private Binder token; + + /** @hide */ + public KeySet(Binder token) { + this.token = token; + } + + Binder getToken() { + return token; + } +} \ No newline at end of file diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4266d85f08e3f5ead924ff687a6a4e20821c6f7e..c97c2b89304deb1ebe251037dd8014658a13679f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -913,6 +913,14 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device is capable of communicating with + * consumer IR devices. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir"; + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports one or more methods of @@ -953,6 +961,26 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_NFC = "android.hardware.nfc"; + /** + * 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. @@ -996,6 +1024,20 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a hardware step counter. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a hardware step detector. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector"; + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device has a telephony radio with data @@ -1174,6 +1216,13 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_INPUT_METHODS = "android.software.input_methods"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports device policy enforcement via device admins. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports WiFi (802.11) networking. @@ -2175,6 +2224,24 @@ public abstract class PackageManager { public abstract List queryIntentServicesAsUser(Intent intent, int flags, int userId); + /** {@hide} */ + public abstract List queryIntentContentProvidersAsUser( + Intent intent, int flags, int userId); + + /** + * Retrieve all providers that can match the given intent. + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags. + * @return A List<ResolveInfo> containing one entry for each matching + * ProviderInfo. These are ordered from best to worst match. If + * there are no matching providers, an empty list is returned. + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + */ + public abstract List queryIntentContentProviders(Intent intent, int flags); + /** * Find a single content provider by its base path name. * @@ -3006,6 +3073,13 @@ public abstract class PackageManager { public abstract int getPreferredActivities(List outFilters, List outActivities, String packageName); + /** + * Ask for the set of available 'home' activities and the current explicit + * default, if any. + * @hide + */ + public abstract ComponentName getHomeActivities(List outActivities); + /** * Set the enabled setting for a package component (activity, receiver, service, provider). * This setting will override any enabled state which may have been set by the component in its @@ -3082,6 +3156,23 @@ public abstract class PackageManager { */ public abstract int getApplicationEnabledSetting(String packageName); + /** + * Puts the package in a blocked state, which is almost like an uninstalled state, + * making the package unavailable, but it doesn't remove the data or the actual + * package file. + * @hide + */ + public abstract boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked, + UserHandle userHandle); + + /** + * Returns the blocked state of a package. + * @see #setApplicationBlockedSettingAsUser(String, boolean, UserHandle) + * @hide + */ + public abstract boolean getApplicationBlockedSettingAsUser(String packageName, + UserHandle userHandle); + /** * Return whether the device has been booted into safe mode. */ @@ -3109,7 +3200,7 @@ public abstract class PackageManager { /** * Returns the device identity that verifiers can use to associate their scheme to a particular * device. This should not be used by anything other than a package verifier. - * + * * @return identity that uniquely identifies current device * @hide */ diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 34e0c124bb962648d1a5ec3639283a6afe92212d..17d13e500e8e8a26d86a7f1e9b744d284d91ded3 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -39,6 +39,7 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; @@ -50,9 +51,12 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; @@ -153,7 +157,8 @@ public class PackageParser { private static WeakReference mReadBuffer; private static boolean sCompatibilityModeEnabled = true; - private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; + private static final int PARSE_DEFAULT_INSTALL_LOCATION = + PackageInfo.INSTALL_LOCATION_UNSPECIFIED; static class ParsePackageItemArgs { final Package owner; @@ -268,15 +273,20 @@ public class PackageParser { grantedPermissions, state, UserHandle.getCallingUserId()); } - private static boolean checkUseInstalled(int flags, PackageUserState state) { - return state.installed || ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0); + /** + * Returns true if the package is installed and not blocked, or if the caller + * explicitly wanted all uninstalled and blocked packages as well. + */ + private static boolean checkUseInstalledOrBlocked(int flags, PackageUserState state) { + return (state.installed && !state.blocked) + || (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; } public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions, PackageUserState state, int userId) { - if (!checkUseInstalled(flags, state)) { + if (!checkUseInstalledOrBlocked(flags, state)) { return null; } PackageInfo pi = new PackageInfo(); @@ -470,6 +480,7 @@ public class PackageParser { public final static int PARSE_FORWARD_LOCK = 1<<4; public final static int PARSE_ON_SDCARD = 1<<5; public final static int PARSE_IS_SYSTEM_DIR = 1<<6; + public final static int PARSE_IS_PRIVILEGED = 1<<7; public int getParseError() { return mParseError; @@ -714,6 +725,13 @@ public class PackageParser { mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; return false; } + + // Add the signing KeySet to the system + pkg.mSigningKeys = new HashSet(); + for (int i=0; i < certs.length; i++) { + pkg.mSigningKeys.add(certs[i].getPublicKey()); + } + } catch (CertificateEncodingException e) { Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; @@ -1027,6 +1045,10 @@ public class PackageParser { if (!parseApplication(pkg, res, parser, attrs, flags, outError)) { return null; } + } else if (tagName.equals("keys")) { + if (!parseKeys(pkg, res, parser, attrs, outError)) { + return null; + } } else if (tagName.equals("permission-group")) { if (parsePermissionGroup(pkg, flags, res, parser, attrs, outError) == null) { return null; @@ -1290,6 +1312,9 @@ public class PackageParser { // Just skip this tag XmlUtils.skipCurrentTag(parser); continue; + } else if (tagName.equals("supports-input")) { + XmlUtils.skipCurrentTag(parser); + continue; } else if (tagName.equals("eat-comment")) { // Just skip this tag @@ -1417,18 +1442,29 @@ public class PackageParser { */ boolean required = true; // Optional not supported + int maxSdkVersion = 0; + TypedValue val = sa.peekValue( + com.android.internal.R.styleable.AndroidManifestUsesPermission_maxSdkVersion); + if (val != null) { + if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) { + maxSdkVersion = val.data; + } + } + sa.recycle(); - if (name != null) { - int index = pkg.requestedPermissions.indexOf(name); - if (index == -1) { - pkg.requestedPermissions.add(name.intern()); - pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE); - } else { - if (pkg.requestedPermissionsRequired.get(index) != required) { - outError[0] = "conflicting entries"; - mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; - return false; + if ((maxSdkVersion == 0) || (maxSdkVersion >= Build.VERSION.RESOURCES_SDK_INT)) { + if (name != null) { + int index = pkg.requestedPermissions.indexOf(name); + if (index == -1) { + pkg.requestedPermissions.add(name.intern()); + pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE); + } else { + if (pkg.requestedPermissionsRequired.get(index) != required) { + outError[0] = "conflicting entries"; + mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; + return false; + } } } } @@ -1519,7 +1555,88 @@ public class PackageParser { } return buildCompoundName(pkg, procSeq, "taskAffinity", outError); } - + + private boolean parseKeys(Package owner, Resources res, + XmlPullParser parser, AttributeSet attrs, String[] outError) + throws XmlPullParserException, IOException { + // we've encountered the 'keys' tag + // all the keys and keysets that we want must be defined here + // so we're going to iterate over the parser and pull out the things we want + int outerDepth = parser.getDepth(); + + int type; + PublicKey currentKey = null; + int currentKeyDepth = -1; + Map> definedKeySets = new HashMap>(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG) { + if (parser.getDepth() == currentKeyDepth) { + currentKey = null; + currentKeyDepth = -1; + } + continue; + } + String tagname = parser.getName(); + if (tagname.equals("publicKey")) { + final TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.PublicKey); + final String encodedKey = sa.getNonResourceString( + com.android.internal.R.styleable.PublicKey_value); + currentKey = parsePublicKey(encodedKey); + if (currentKey == null) { + Slog.w(TAG, "No valid key in 'publicKey' tag at " + + parser.getPositionDescription()); + sa.recycle(); + continue; + } + currentKeyDepth = parser.getDepth(); + definedKeySets.put(currentKey, new HashSet()); + sa.recycle(); + } else if (tagname.equals("keyset")) { + if (currentKey == null) { + Slog.i(TAG, "'keyset' not in 'publicKey' tag at " + + parser.getPositionDescription()); + continue; + } + final TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.KeySet); + final String name = sa.getNonResourceString( + com.android.internal.R.styleable.KeySet_name); + definedKeySets.get(currentKey).add(name); + sa.recycle(); + } else if (RIGID_PARSER) { + Slog.w(TAG, "Bad element under : " + parser.getName() + + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + return false; + } else { + Slog.w(TAG, "Unknown element under : " + parser.getName() + + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } + } + + owner.mKeySetMapping = new HashMap>(); + for (Map.Entry> e : definedKeySets.entrySet()) { + PublicKey key = e.getKey(); + Set keySetNames = e.getValue(); + for (String alias : keySetNames) { + if (owner.mKeySetMapping.containsKey(alias)) { + owner.mKeySetMapping.get(alias).add(key); + } else { + Set keys = new HashSet(); + keys.add(key); + owner.mKeySetMapping.put(alias, keys); + } + } + } + + return true; + } + private PermissionGroup parsePermissionGroup(Package owner, int flags, Resources res, XmlPullParser parser, AttributeSet attrs, String[] outError) throws XmlPullParserException, IOException { @@ -1759,7 +1876,8 @@ public class PackageParser { } String manageSpaceActivity = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity, 0); + com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity, + Configuration.NATIVE_CONFIG_VERSION); if (manageSpaceActivity != null) { ai.manageSpaceActivityName = buildClassName(pkgName, manageSpaceActivity, outError); @@ -1773,7 +1891,8 @@ public class PackageParser { // backupAgent, killAfterRestore, and restoreAnyVersion are only relevant // if backup is possible for the given application. String backupAgent = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestApplication_backupAgent, 0); + com.android.internal.R.styleable.AndroidManifestApplication_backupAgent, + Configuration.NATIVE_CONFIG_VERSION); if (backupAgent != null) { ai.backupAgentName = buildClassName(pkgName, backupAgent, outError); if (DEBUG_BACKUP) { @@ -1894,7 +2013,8 @@ public class PackageParser { if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) { str = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity, 0); + com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity, + Configuration.NATIVE_CONFIG_VERSION); } else { // Some older apps have been seen to use a resource reference // here that on older builds was ignored (with a warning). We @@ -1909,7 +2029,8 @@ public class PackageParser { CharSequence pname; if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) { pname = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestApplication_process, 0); + com.android.internal.R.styleable.AndroidManifestApplication_process, + Configuration.NATIVE_CONFIG_VERSION); } else { // Some older apps have been seen to use a resource reference // here that on older builds was ignored (with a warning). We @@ -2173,7 +2294,8 @@ public class PackageParser { a.info.applicationInfo.uiOptions); String parentName = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestActivity_parentActivityName, 0); + com.android.internal.R.styleable.AndroidManifestActivity_parentActivityName, + Configuration.NATIVE_CONFIG_VERSION); if (parentName != null) { String parentClassName = buildClassName(a.info.packageName, parentName, outError); if (outError[0] == null) { @@ -2195,7 +2317,8 @@ public class PackageParser { } str = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity, 0); + com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity, + Configuration.NATIVE_CONFIG_VERSION); a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName, owner.applicationInfo.taskAffinity, str, outError); @@ -2335,7 +2458,7 @@ public class PackageParser { if (parser.getName().equals("intent-filter")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); - if (!parseIntent(res, parser, attrs, flags, intent, outError, !receiver)) { + if (!parseIntent(res, parser, attrs, true, intent, outError)) { return null; } if (intent.countActions() == 0) { @@ -2345,6 +2468,21 @@ public class PackageParser { } else { a.intents.add(intent); } + } else if (!receiver && parser.getName().equals("preferred")) { + ActivityIntentInfo intent = new ActivityIntentInfo(a); + if (!parseIntent(res, parser, attrs, false, intent, outError)) { + return null; + } + if (intent.countActions() == 0) { + Slog.w(TAG, "No actions in preferred at " + + mArchiveSourcePath + " " + + parser.getPositionDescription()); + } else { + if (owner.preferredActivityFilters == null) { + owner.preferredActivityFilters = new ArrayList(); + } + owner.preferredActivityFilters.add(intent); + } } else if (parser.getName().equals("meta-data")) { if ((a.metaData=parseMetaData(res, parser, attrs, a.metaData, outError)) == null) { @@ -2389,7 +2527,8 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivityAlias); String targetActivity = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity, 0); + com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity, + Configuration.NATIVE_CONFIG_VERSION); if (targetActivity == null) { outError[0] = " does not specify android:targetActivity"; sa.recycle(); @@ -2479,7 +2618,7 @@ public class PackageParser { String parentName = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestActivityAlias_parentActivityName, - 0); + Configuration.NATIVE_CONFIG_VERSION); if (parentName != null) { String parentClassName = buildClassName(a.info.packageName, parentName, outError); if (outError[0] == null) { @@ -2508,7 +2647,7 @@ public class PackageParser { if (parser.getName().equals("intent-filter")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); - if (!parseIntent(res, parser, attrs, flags, intent, outError, true)) { + if (!parseIntent(res, parser, attrs, true, intent, outError)) { return null; } if (intent.countActions() == 0) { @@ -2680,7 +2819,14 @@ public class PackageParser { continue; } - if (parser.getName().equals("meta-data")) { + if (parser.getName().equals("intent-filter")) { + ProviderIntentInfo intent = new ProviderIntentInfo(outInfo); + if (!parseIntent(res, parser, attrs, true, intent, outError)) { + return false; + } + outInfo.intents.add(intent); + + } else if (parser.getName().equals("meta-data")) { if ((outInfo.metaData=parseMetaData(res, parser, attrs, outInfo.metaData, outError)) == null) { return false; @@ -2932,7 +3078,7 @@ public class PackageParser { if (parser.getName().equals("intent-filter")) { ServiceIntentInfo intent = new ServiceIntentInfo(s); - if (!parseIntent(res, parser, attrs, flags, intent, outError, false)) { + if (!parseIntent(res, parser, attrs, true, intent, outError)) { return null; } @@ -3083,20 +3229,28 @@ public class PackageParser { Slog.i(TAG, "verifier " + packageName + " public key was null; skipping"); } + PublicKey publicKey = parsePublicKey(encodedPublicKey); + if (publicKey != null) { + return new VerifierInfo(packageName, publicKey); + } + + return null; + } + + public static final PublicKey parsePublicKey(String encodedPublicKey) { EncodedKeySpec keySpec; try { final byte[] encoded = Base64.decode(encodedPublicKey, Base64.DEFAULT); keySpec = new X509EncodedKeySpec(encoded); } catch (IllegalArgumentException e) { - Slog.i(TAG, "Could not parse verifier " + packageName + " public key; invalid Base64"); + Slog.i(TAG, "Could not parse verifier public key; invalid Base64"); return null; } /* First try the key as an RSA key. */ try { final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - final PublicKey publicKey = keyFactory.generatePublic(keySpec); - return new VerifierInfo(packageName, publicKey); + return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Could not parse public key because RSA isn't included in build"); return null; @@ -3107,8 +3261,7 @@ public class PackageParser { /* Now try it as a DSA key. */ try { final KeyFactory keyFactory = KeyFactory.getInstance("DSA"); - final PublicKey publicKey = keyFactory.generatePublic(keySpec); - return new VerifierInfo(packageName, publicKey); + return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Could not parse public key because DSA isn't included in build"); return null; @@ -3122,9 +3275,8 @@ public class PackageParser { private static final String ANDROID_RESOURCES = "http://schemas.android.com/apk/res/android"; - private boolean parseIntent(Resources res, - XmlPullParser parser, AttributeSet attrs, int flags, - IntentInfo outInfo, String[] outError, boolean isActivity) + private boolean parseIntent(Resources res, XmlPullParser parser, AttributeSet attrs, + boolean allowGlobs, IntentInfo outInfo, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(attrs, @@ -3200,6 +3352,28 @@ public class PackageParser { outInfo.addDataScheme(str); } + str = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifestData_ssp, 0); + if (str != null) { + outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_LITERAL); + } + + str = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifestData_sspPrefix, 0); + if (str != null) { + outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_PREFIX); + } + + str = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifestData_sspPattern, 0); + if (str != null) { + if (!allowGlobs) { + outError[0] = "sspPattern not allowed here; ssp must be literal"; + return false; + } + outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_SIMPLE_GLOB); + } + String host = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_host, 0); String port = sa.getNonConfigurationString( @@ -3223,6 +3397,10 @@ public class PackageParser { str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_pathPattern, 0); if (str != null) { + if (!allowGlobs) { + outError[0] = "pathPattern not allowed here; path must be literal"; + return false; + } outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB); } @@ -3284,6 +3462,8 @@ public class PackageParser { public ArrayList usesOptionalLibraries = null; public String[] usesLibraryFiles = null; + public ArrayList preferredActivityFilters = null; + public ArrayList mOriginalPackages = null; public String mRealPackage = null; public ArrayList mAdoptPermissions = null; @@ -3360,6 +3540,12 @@ public class PackageParser { */ public ManifestDigest manifestDigest; + /** + * Data used to feed the KeySetManager + */ + public Set mSigningKeys; + public Map> mKeySetMapping; + public Package(String _name) { packageName = _name; applicationInfo.packageName = _name; @@ -3491,7 +3677,8 @@ public class PackageParser { if (args.processRes != 0) { CharSequence pname; if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) { - pname = args.sa.getNonConfigurationString(args.processRes, 0); + pname = args.sa.getNonConfigurationString(args.processRes, + Configuration.NATIVE_CONFIG_VERSION); } else { // Some older apps have been seen to use a resource reference // here that on older builds was ignored (with a warning). We @@ -3528,18 +3715,15 @@ public class PackageParser { } return componentName; } - - public String getComponentShortName() { - if (componentShortName != null) { - return componentShortName; - } - ComponentName component = getComponentName(); - if (component != null) { - componentShortName = component.flattenToShortString(); - } - return componentShortName; + + public void appendComponentShortName(StringBuilder sb) { + ComponentName.appendShortString(sb, owner.applicationInfo.packageName, className); } - + + public void printComponentShortName(PrintWriter pw) { + ComponentName.printShortString(pw, owner.applicationInfo.packageName, className); + } + public void setPackageName(String packageName) { componentName = null; componentShortName = null; @@ -3611,7 +3795,7 @@ public class PackageParser { return true; } } - if (!state.installed) { + if (!state.installed || state.blocked) { return true; } if (state.stopped) { @@ -3644,6 +3828,11 @@ public class PackageParser { } else { ai.flags &= ~ApplicationInfo.FLAG_INSTALLED; } + if (state.blocked) { + ai.flags |= ApplicationInfo.FLAG_BLOCKED; + } else { + ai.flags &= ~ApplicationInfo.FLAG_BLOCKED; + } if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { ai.enabled = true; } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { @@ -3658,7 +3847,7 @@ public class PackageParser { public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state, int userId) { if (p == null) return null; - if (!checkUseInstalled(flags, state)) { + if (!checkUseInstalledOrBlocked(flags, state)) { return null; } if (!copyNeeded(flags, p, state, null, userId) @@ -3733,16 +3922,20 @@ public class PackageParser { } public String toString() { - return "Activity{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + getComponentShortName() + "}"; + StringBuilder sb = new StringBuilder(128); + sb.append("Activity{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); } } public static final ActivityInfo generateActivityInfo(Activity a, int flags, PackageUserState state, int userId) { if (a == null) return null; - if (!checkUseInstalled(flags, state)) { + if (!checkUseInstalledOrBlocked(flags, state)) { return null; } if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) { @@ -3770,16 +3963,20 @@ public class PackageParser { } public String toString() { - return "Service{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + getComponentShortName() + "}"; + StringBuilder sb = new StringBuilder(128); + sb.append("Service{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); } } public static final ServiceInfo generateServiceInfo(Service s, int flags, PackageUserState state, int userId) { if (s == null) return null; - if (!checkUseInstalled(flags, state)) { + if (!checkUseInstalledOrBlocked(flags, state)) { return null; } if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) { @@ -3792,7 +3989,7 @@ public class PackageParser { return si; } - public final static class Provider extends Component { + public final static class Provider extends Component { public final ProviderInfo info; public boolean syncable; @@ -3815,16 +4012,20 @@ public class PackageParser { } public String toString() { - return "Provider{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + info.name + "}"; + StringBuilder sb = new StringBuilder(128); + sb.append("Provider{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); } } public static final ProviderInfo generateProviderInfo(Provider p, int flags, PackageUserState state, int userId) { if (p == null) return null; - if (!checkUseInstalled(flags, state)) { + if (!checkUseInstalledOrBlocked(flags, state)) { return null; } if (!copyNeeded(flags, p.owner, state, p.metaData, userId) @@ -3856,9 +4057,13 @@ public class PackageParser { } public String toString() { - return "Instrumentation{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + getComponentShortName() + "}"; + StringBuilder sb = new StringBuilder(128); + sb.append("Instrumentation{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); } } @@ -3879,6 +4084,7 @@ public class PackageParser { public CharSequence nonLocalizedLabel; public int icon; public int logo; + public int preferred; } public final static class ActivityIntentInfo extends IntentInfo { @@ -3889,9 +4095,13 @@ public class PackageParser { } public String toString() { - return "ActivityIntentInfo{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + activity.info.name + "}"; + StringBuilder sb = new StringBuilder(128); + sb.append("ActivityIntentInfo{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + activity.appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); } } @@ -3903,9 +4113,31 @@ public class PackageParser { } public String toString() { - return "ServiceIntentInfo{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + service.info.name + "}"; + StringBuilder sb = new StringBuilder(128); + sb.append("ServiceIntentInfo{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + service.appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); + } + } + + public static final class ProviderIntentInfo extends IntentInfo { + public final Provider provider; + + public ProviderIntentInfo(Provider provider) { + this.provider = provider; + } + + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("ProviderIntentInfo{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + provider.appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); } } diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index dcd54fcb585281605b61906146b8f7596a2fec49..94e3f79bbe749e6a1eab9bc434ee76974660a568 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -28,6 +28,7 @@ public class PackageUserState { public boolean stopped; public boolean notLaunched; public boolean installed; + public boolean blocked; // Is the app restricted by owner / admin public int enabled; public String lastDisableAppCaller; @@ -37,6 +38,7 @@ public class PackageUserState { public PackageUserState() { installed = true; + blocked = false; enabled = COMPONENT_ENABLED_STATE_DEFAULT; } @@ -45,6 +47,7 @@ public class PackageUserState { stopped = o.stopped; notLaunched = o.notLaunched; enabled = o.enabled; + blocked = o.blocked; lastDisableAppCaller = o.lastDisableAppCaller; disabledComponents = o.disabledComponents != null ? new HashSet(o.disabledComponents) : null; diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index a53417620cc080e92707d6ca046ab260f1a8e0cf..f6ea058b526f2e8c3968bc65a20273209bcbc718 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; +import android.util.Printer; /** * Holds information about a specific @@ -112,7 +113,13 @@ public final class ProviderInfo extends ComponentInfo flags = orig.flags; isSyncable = orig.isSyncable; } - + + public void dump(Printer pw, String prefix) { + super.dumpFront(pw, prefix); + pw.println(prefix + "authority=" + authority); + pw.println(prefix + "flags=0x" + Integer.toHexString(flags)); + } + public int describeContents() { return 0; } diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 288d55f577f673ce0f8929e1b21682a01d0fb602..875e8de2daed52a8b02b67ba6023bf286056421f 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -82,7 +82,7 @@ public abstract class RegisteredServicesCache { @GuardedBy("mServicesLock") private boolean mPersistentServicesFileDidNotExist; @GuardedBy("mServicesLock") - private final SparseArray> mUserServices = new SparseArray>(); + private final SparseArray> mUserServices = new SparseArray>(2); private static class UserServices { @GuardedBy("mServicesLock") diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index 07117fe0754e8d2276fa4ed9f3a046beb18c5b73..1ff41c022186aa757a4023c56db8ecc86236a4d3 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -16,12 +16,14 @@ package android.content.pm; +import android.content.ComponentName; import android.content.IntentFilter; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Printer; +import android.util.Slog; import java.text.Collator; import java.util.Comparator; @@ -33,20 +35,30 @@ import java.util.Comparator; * <intent> tags. */ public class ResolveInfo implements Parcelable { + private static final String TAG = "ResolveInfo"; + /** - * The activity or broadcast receiver that corresponds to this resolution match, - * if this resolution is for an activity or broadcast receiver. One and only one of this and - * serviceInfo must be non-null. + * The activity or broadcast receiver that corresponds to this resolution + * match, if this resolution is for an activity or broadcast receiver. + * Exactly one of {@link #activityInfo}, {@link #serviceInfo}, or + * {@link #providerInfo} will be non-null. */ public ActivityInfo activityInfo; /** - * The service that corresponds to this resolution match, if this - * resolution is for a service. One and only one of this and - * activityInfo must be non-null. + * The service that corresponds to this resolution match, if this resolution + * is for a service. Exactly one of {@link #activityInfo}, + * {@link #serviceInfo}, or {@link #providerInfo} will be non-null. */ public ServiceInfo serviceInfo; - + + /** + * The provider that corresponds to this resolution match, if this + * resolution is for a provider. Exactly one of {@link #activityInfo}, + * {@link #serviceInfo}, or {@link #providerInfo} will be non-null. + */ + public ProviderInfo providerInfo; + /** * The IntentFilter that was matched for this ResolveInfo. */ @@ -119,6 +131,13 @@ public class ResolveInfo implements Parcelable { */ public boolean system; + private ComponentInfo getComponentInfo() { + if (activityInfo != null) return activityInfo; + if (serviceInfo != null) return serviceInfo; + if (providerInfo != null) return providerInfo; + throw new IllegalStateException("Missing ComponentInfo!"); + } + /** * Retrieve the current textual label associated with this resolution. This * will call back on the given PackageManager to load the label from @@ -141,7 +160,7 @@ public class ResolveInfo implements Parcelable { return label.toString().trim(); } } - ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo; + ComponentInfo ci = getComponentInfo(); ApplicationInfo ai = ci.applicationInfo; if (labelRes != 0) { label = pm.getText(ci.packageName, labelRes, ai); @@ -175,7 +194,7 @@ public class ResolveInfo implements Parcelable { return dr; } } - ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo; + ComponentInfo ci = getComponentInfo(); ApplicationInfo ai = ci.applicationInfo; if (icon != 0) { dr = pm.getDrawable(ci.packageName, icon, ai); @@ -195,8 +214,8 @@ public class ResolveInfo implements Parcelable { */ public final int getIconResource() { if (icon != 0) return icon; - if (activityInfo != null) return activityInfo.getIconResource(); - if (serviceInfo != null) return serviceInfo.getIconResource(); + final ComponentInfo ci = getComponentInfo(); + if (ci != null) return ci.getIconResource(); return 0; } @@ -224,6 +243,9 @@ public class ResolveInfo implements Parcelable { } else if (serviceInfo != null) { pw.println(prefix + "ServiceInfo:"); serviceInfo.dump(pw, prefix + " "); + } else if (providerInfo != null) { + pw.println(prefix + "ProviderInfo:"); + providerInfo.dump(pw, prefix + " "); } } @@ -233,6 +255,7 @@ public class ResolveInfo implements Parcelable { public ResolveInfo(ResolveInfo orig) { activityInfo = orig.activityInfo; serviceInfo = orig.serviceInfo; + providerInfo = orig.providerInfo; filter = orig.filter; priority = orig.priority; preferredOrder = orig.preferredOrder; @@ -246,11 +269,24 @@ public class ResolveInfo implements Parcelable { } public String toString() { - ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo; - return "ResolveInfo{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + ci.name + " p=" + priority + " o=" - + preferredOrder + " m=0x" + Integer.toHexString(match) + "}"; + final ComponentInfo ci = getComponentInfo(); + StringBuilder sb = new StringBuilder(128); + sb.append("ResolveInfo{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + ComponentName.appendShortString(sb, ci.packageName, ci.name); + if (priority != 0) { + sb.append(" p="); + sb.append(priority); + } + if (preferredOrder != 0) { + sb.append(" o="); + sb.append(preferredOrder); + } + sb.append(" m=0x"); + sb.append(Integer.toHexString(match)); + sb.append('}'); + return sb.toString(); } public int describeContents() { @@ -264,6 +300,9 @@ public class ResolveInfo implements Parcelable { } else if (serviceInfo != null) { dest.writeInt(2); serviceInfo.writeToParcel(dest, parcelableFlags); + } else if (providerInfo != null) { + dest.writeInt(3); + providerInfo.writeToParcel(dest, parcelableFlags); } else { dest.writeInt(0); } @@ -295,18 +334,21 @@ public class ResolveInfo implements Parcelable { }; private ResolveInfo(Parcel source) { + activityInfo = null; + serviceInfo = null; + providerInfo = null; switch (source.readInt()) { case 1: activityInfo = ActivityInfo.CREATOR.createFromParcel(source); - serviceInfo = null; break; case 2: serviceInfo = ServiceInfo.CREATOR.createFromParcel(source); - activityInfo = null; + break; + case 3: + providerInfo = ProviderInfo.CREATOR.createFromParcel(source); break; default: - activityInfo = null; - serviceInfo = null; + Slog.w(TAG, "Missing ComponentInfo!"); break; } if (source.readInt() != 0) { @@ -328,6 +370,7 @@ public class ResolveInfo implements Parcelable { implements Comparator { public DisplayNameComparator(PackageManager pm) { mPM = pm; + mCollator.setStrength(Collator.PRIMARY); } public final int compare(ResolveInfo a, ResolveInfo b) { @@ -336,10 +379,10 @@ public class ResolveInfo implements Parcelable { CharSequence sb = b.loadLabel(mPM); if (sb == null) sb = b.activityInfo.name; - return sCollator.compare(sa.toString(), sb.toString()); + return mCollator.compare(sa.toString(), sb.toString()); } - private final Collator sCollator = Collator.getInstance(); + private final Collator mCollator = Collator.getInstance(); private PackageManager mPM; } } diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java index 7d46710b2c5847db9adac7016909f34e32e6dfff..28edde0250cfef4b9e886c9a5873e7faff0dee46 100644 --- a/core/java/android/content/res/AssetFileDescriptor.java +++ b/core/java/android/content/res/AssetFileDescriptor.java @@ -16,10 +16,12 @@ package android.content.res; +import android.os.Bundle; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import java.io.Closeable; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -30,7 +32,7 @@ import java.io.IOException; * opened FileDescriptor that can be used to read the data, as well as the * offset and length of that entry's data in the file. */ -public class AssetFileDescriptor implements Parcelable { +public class AssetFileDescriptor implements Parcelable, Closeable { /** * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)} * and {@link #getDeclaredLength} when a length has not been declared. This means @@ -41,17 +43,35 @@ public class AssetFileDescriptor implements Parcelable { private final ParcelFileDescriptor mFd; private final long mStartOffset; private final long mLength; - + private final Bundle mExtras; + /** * Create a new AssetFileDescriptor from the given values. + * * @param fd The underlying file descriptor. * @param startOffset The location within the file that the asset starts. - * This must be 0 if length is UNKNOWN_LENGTH. + * This must be 0 if length is UNKNOWN_LENGTH. * @param length The number of bytes of the asset, or - * {@link #UNKNOWN_LENGTH} if it extends to the end of the file. + * {@link #UNKNOWN_LENGTH} if it extends to the end of the file. */ public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset, long length) { + this(fd, startOffset, length, null); + } + + /** + * Create a new AssetFileDescriptor from the given values. + * + * @param fd The underlying file descriptor. + * @param startOffset The location within the file that the asset starts. + * This must be 0 if length is UNKNOWN_LENGTH. + * @param length The number of bytes of the asset, or + * {@link #UNKNOWN_LENGTH} if it extends to the end of the file. + * @param extras additional details that can be used to interpret the + * underlying file descriptor. May be null. + */ + public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset, + long length, Bundle extras) { if (fd == null) { throw new IllegalArgumentException("fd must not be null"); } @@ -62,8 +82,9 @@ public class AssetFileDescriptor implements Parcelable { mFd = fd; mStartOffset = startOffset; mLength = length; + mExtras = extras; } - + /** * The AssetFileDescriptor contains its own ParcelFileDescriptor, which * in addition to the normal FileDescriptor object also allows you to close @@ -87,7 +108,15 @@ public class AssetFileDescriptor implements Parcelable { public long getStartOffset() { return mStartOffset; } - + + /** + * Returns any additional details that can be used to interpret the + * underlying file descriptor. May be null. + */ + public Bundle getExtras() { + return mExtras; + } + /** * Returns the total number of bytes of this asset entry's data. May be * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file. @@ -122,6 +151,7 @@ public class AssetFileDescriptor implements Parcelable { /** * Convenience for calling getParcelFileDescriptor().close(). */ + @Override public void close() throws IOException { mFd.close(); } @@ -305,25 +335,37 @@ public class AssetFileDescriptor implements Parcelable { super.write(oneByte); } } - - + /* Parcelable interface */ + @Override public int describeContents() { return mFd.describeContents(); } + @Override public void writeToParcel(Parcel out, int flags) { mFd.writeToParcel(out, flags); out.writeLong(mStartOffset); out.writeLong(mLength); + if (mExtras != null) { + out.writeInt(1); + out.writeBundle(mExtras); + } else { + out.writeInt(0); + } } AssetFileDescriptor(Parcel src) { mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src); mStartOffset = src.readLong(); mLength = src.readLong(); + if (src.readInt() != 0) { + mExtras = src.readBundle(); + } else { + mExtras = null; + } } - + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public AssetFileDescriptor createFromParcel(Parcel in) { diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 28c751c373fe744e8bd720c5df41da681c676271..da35ee92267e404a9b06812a3b5221a832819723 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -471,8 +471,7 @@ public class CompatibilityInfo implements Parcelable { * Compute the frame Rect for applications runs under compatibility mode. * * @param dm the display metrics used to compute the frame size. - * @param orientation the orientation of the screen. - * @param outRect the output parameter which will contain the result. + * @param outDm If non-null the width and height will be set to their scaled values. * @return Returns the scaling factor for the window. */ public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) { @@ -518,6 +517,9 @@ public class CompatibilityInfo implements Parcelable { @Override public boolean equals(Object o) { + if (this == o) { + return true; + } try { CompatibilityInfo oc = (CompatibilityInfo)o; if (mCompatibilityFlags != oc.mCompatibilityFlags) return false; @@ -579,10 +581,12 @@ public class CompatibilityInfo implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public CompatibilityInfo createFromParcel(Parcel source) { return new CompatibilityInfo(source); } + @Override public CompatibilityInfo[] newArray(int size) { return new CompatibilityInfo[size]; } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 6318e3893767c73e6cdcecc4e1085a13428ded32..48b6fca7b2d32bff5608d9569a676ef8f61486b5 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -544,7 +544,40 @@ public final class Configuration implements Parcelable, Comparable > mDrawableCache - = new LongSparseArray >(); + = new LongSparseArray >(0); /*package*/ final LongSparseArray > mColorStateListCache - = new LongSparseArray >(); + = new LongSparseArray >(0); /*package*/ final LongSparseArray > mColorDrawableCache - = new LongSparseArray >(); + = new LongSparseArray >(0); /*package*/ boolean mPreloading; /*package*/ TypedArray mCachedStyledAttributes = null; @@ -118,8 +119,9 @@ public class Resources { private final Configuration mConfiguration = new Configuration(); /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); private NativePluralRules mPluralRule; - - private CompatibilityInfo mCompatibilityInfo; + + private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + private WeakReference mToken; static { sPreloadedDrawables = new LongSparseArray[2]; @@ -174,7 +176,7 @@ public class Resources { * selecting/computing resource values (optional). */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { - this(assets, metrics, config, null); + this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); } /** @@ -185,15 +187,18 @@ public class Resources { * selecting/computing resource values. * @param config Desired device configuration to consider when * selecting/computing resource values (optional). - * @param compInfo this resource's compatibility info. It will use the default compatibility - * info when it's null. + * @param compatInfo this resource's compatibility info. Must not be null. + * @param token The Activity token for determining stack affiliation. Usually null. * @hide */ - public Resources(AssetManager assets, DisplayMetrics metrics, - Configuration config, CompatibilityInfo compInfo) { + public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config, + CompatibilityInfo compatInfo, IBinder token) { mAssets = assets; mMetrics.setToDefaults(); - mCompatibilityInfo = compInfo; + if (compatInfo != null) { + mCompatibilityInfo = compatInfo; + } + mToken = new WeakReference(token); updateConfiguration(config, metrics); assets.ensureStringBlocks(); } @@ -1533,9 +1538,8 @@ public class Resources { // it would be cleaner and more maintainble to just be // consistently dealing with a compatible display everywhere in // the framework. - if (mCompatibilityInfo != null) { - mCompatibilityInfo.applyToDisplayMetrics(mMetrics); - } + mCompatibilityInfo.applyToDisplayMetrics(mMetrics); + int configChanges = 0xfffffff; if (config != null) { mTmpConfig.setTo(config); @@ -1543,9 +1547,9 @@ public class Resources { if (density == Configuration.DENSITY_DPI_UNDEFINED) { density = mMetrics.noncompatDensityDpi; } - if (mCompatibilityInfo != null) { - mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); - } + + mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); + if (mTmpConfig.locale == null) { mTmpConfig.locale = Locale.getDefault(); mTmpConfig.setLayoutDirection(mTmpConfig.locale); @@ -1664,13 +1668,6 @@ public class Resources { } } - /** - * @hide - */ - public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) { - updateSystemConfiguration(config, metrics, null); - } - /** * Return the current display metrics that are in effect for this resource * object. The returned object should be treated as read-only. @@ -1701,8 +1698,7 @@ public class Resources { * @hide */ public CompatibilityInfo getCompatibilityInfo() { - return mCompatibilityInfo != null ? mCompatibilityInfo - : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + return mCompatibilityInfo; } /** @@ -1710,8 +1706,10 @@ public class Resources { * @hide */ public void setCompatibilityInfo(CompatibilityInfo ci) { - mCompatibilityInfo = ci; - updateConfiguration(mConfiguration, mMetrics); + if (ci != null) { + mCompatibilityInfo = ci; + updateConfiguration(mConfiguration, mMetrics); + } } /** @@ -1985,6 +1983,13 @@ public class Resources { } } + /** + * @hide + */ + public LongSparseArray getPreloadedDrawables() { + return sPreloadedDrawables[0]; + } + private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying, int resourceId, String name) { // We allow preloading of resources even if they vary by font scale (which @@ -2404,6 +2409,5 @@ public class Resources { mMetrics.setToDefaults(); updateConfiguration(null, null); mAssets.ensureStringBlocks(); - mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; } } diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java new file mode 100644 index 0000000000000000000000000000000000000000..53e0f2c00e0d9ee7354cba459a0e8e21a10b6924 --- /dev/null +++ b/core/java/android/content/res/ResourcesKey.java @@ -0,0 +1,89 @@ +/* + * 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.res; + +import android.os.IBinder; + +/** @hide */ +public final class ResourcesKey { + final String mResDir; + final float mScale; + private final int mHash; + private final IBinder mToken; + + public final int mDisplayId; + public final Configuration mOverrideConfiguration = new Configuration(); + + public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, + float scale, IBinder token) { + mResDir = resDir; + mDisplayId = displayId; + if (overrideConfiguration != null) { + mOverrideConfiguration.setTo(overrideConfiguration); + } + mScale = scale; + mToken = token; + + int hash = 17; + hash = 31 * hash + (mResDir == null ? 0 : mResDir.hashCode()); + hash = 31 * hash + mDisplayId; + hash = 31 * hash + (mOverrideConfiguration != null + ? mOverrideConfiguration.hashCode() : 0); + hash = 31 * hash + Float.floatToIntBits(mScale); + mHash = hash; + } + + public boolean hasOverrideConfiguration() { + return !Configuration.EMPTY.equals(mOverrideConfiguration); + } + + @Override + public int hashCode() { + return mHash; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ResourcesKey)) { + return false; + } + ResourcesKey peer = (ResourcesKey) obj; + if (!mResDir.equals(peer.mResDir)) { + return false; + } + if (mDisplayId != peer.mDisplayId) { + return false; + } + if (mOverrideConfiguration != peer.mOverrideConfiguration) { + if (mOverrideConfiguration == null || peer.mOverrideConfiguration == null) { + return false; + } + if (!mOverrideConfiguration.equals(peer.mOverrideConfiguration)) { + return false; + } + } + if (mScale != peer.mScale) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toHexString(mHash); + } +} diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 27dddd47aa32eb5d5add4b794ba0f7226d6fc5f7..83d48aa4654237f3abf6350c860e514d42071b02 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -16,6 +16,7 @@ package android.content.res; +import android.content.pm.ActivityInfo; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -170,8 +171,8 @@ public class TypedArray { * * @param index Index of attribute to retrieve. * @param allowedChangingConfigs Bit mask of configurations from - * ActivityInfo that are allowed to change. - * + * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change. + * * @return String holding string data. Any styling information is * removed. Returns null if the attribute is not defined. */ diff --git a/core/java/android/content/res/XmlResourceParser.java b/core/java/android/content/res/XmlResourceParser.java index c59e6d4e0e015dde22e7ee6acbd1fd7eb92665d7..5af49d4d46a29fdeec08773c807e0f7b8314b3d1 100644 --- a/core/java/android/content/res/XmlResourceParser.java +++ b/core/java/android/content/res/XmlResourceParser.java @@ -26,7 +26,7 @@ import android.util.AttributeSet; * an additional close() method on this interface for the client to indicate * when it is done reading the resource. */ -public interface XmlResourceParser extends XmlPullParser, AttributeSet { +public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable { /** * Close this interface to the resource. Calls on the interface are no * longer value after this call. diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index 300b4d1982df1ddf2f8eb912021fd24be69326c1..b5b89dd17a5d285f5db83c4fbe89fb26d8d9c1cf 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -369,7 +369,9 @@ public abstract class AbstractCursor implements CrossProcessCursor { } public Uri getNotificationUri() { - return mNotifyUri; + synchronized (mSelfObserverLock) { + return mNotifyUri; + } } public boolean getWantsAllOnMoveCalls() { diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java index 907833d9d49d964a361c82a4fdf374a5f36ea55e..fc2a8852021708b48f7bd207eb191ba6f8b32576 100644 --- a/core/java/android/database/Cursor.java +++ b/core/java/android/database/Cursor.java @@ -25,9 +25,12 @@ import java.io.Closeable; /** * This interface provides random read-write access to the result set returned * by a database query. - * + *

    * Cursor implementations are not required to be synchronized so code using a Cursor from multiple * threads should perform its own synchronization when using the Cursor. + *

    + * Implementations should subclass {@link AbstractCursor}. + *

    */ public interface Cursor extends Closeable { /* @@ -424,6 +427,16 @@ public interface Cursor extends Closeable { */ void setNotificationUri(ContentResolver cr, Uri uri); + /** + * Return the URI at which notifications of changes in this Cursor's data + * will be delivered, as previously set by {@link #setNotificationUri}. + * @return Returns a URI that can be used with + * {@link ContentResolver#registerContentObserver(android.net.Uri, boolean, ContentObserver) + * ContentResolver.registerContentObserver} to find out about changes to this Cursor's + * data. May be null if no notification URI has been set. + */ + Uri getNotificationUri(); + /** * onMove() will only be called across processes if this method returns true. * @return whether all cursor movement should result in a call to onMove(). diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java index 7baeb8c2ed68cc9e2ebb67f8a067a534079bf5e7..d8fcb17a19231163459c543d7c3f380b9f4f7925 100644 --- a/core/java/android/database/CursorWrapper.java +++ b/core/java/android/database/CursorWrapper.java @@ -194,6 +194,10 @@ public class CursorWrapper implements Cursor { mCursor.setNotificationUri(cr, uri); } + public Uri getNotificationUri() { + return mCursor.getNotificationUri(); + } + public void unregisterContentObserver(ContentObserver observer) { mCursor.unregisterContentObserver(observer); } diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java index 6e68b6bbbfcb3005400f33189cfc29311a0cf9df..5e107f23d3d46cdc682ef69d6b89f5ad08c1e04c 100644 --- a/core/java/android/database/MatrixCursor.java +++ b/core/java/android/database/MatrixCursor.java @@ -83,11 +83,10 @@ public class MatrixCursor extends AbstractCursor { * row */ public RowBuilder newRow() { - rowCount++; - int endIndex = rowCount * columnCount; + final int row = rowCount++; + final int endIndex = rowCount * columnCount; ensureCapacity(endIndex); - int start = endIndex - columnCount; - return new RowBuilder(start, endIndex); + return new RowBuilder(row); } /** @@ -180,18 +179,29 @@ public class MatrixCursor extends AbstractCursor { } /** - * Builds a row, starting from the left-most column and adding one column - * value at a time. Follows the same ordering as the column names specified - * at cursor construction time. + * Builds a row of values using either of these approaches: + *
      + *
    • Values can be added with explicit column ordering using + * {@link #add(Object)}, which starts from the left-most column and adds one + * column value at a time. This follows the same ordering as the column + * names specified at cursor construction time. + *
    • Column and value pairs can be offered for possible inclusion using + * {@link #add(String, Object)}. If the cursor includes the given column, + * the value will be set for that column, otherwise the value is ignored. + * This approach is useful when matching data to a custom projection. + *
    + * Undefined values are left as {@code null}. */ public class RowBuilder { + private final int row; + private final int endIndex; private int index; - private final int endIndex; - RowBuilder(int index, int endIndex) { - this.index = index; - this.endIndex = endIndex; + RowBuilder(int row) { + this.row = row; + this.index = row * columnCount; + this.endIndex = index + columnCount; } /** @@ -210,6 +220,21 @@ public class MatrixCursor extends AbstractCursor { data[index++] = columnValue; return this; } + + /** + * Offer value for possible inclusion if this cursor defines the given + * column. Columns not defined by the cursor are silently ignored. + * + * @return this builder to support chaining + */ + public RowBuilder add(String columnName, Object value) { + for (int i = 0; i < columnNames.length; i++) { + if (columnName.equals(columnNames[i])) { + data[(row * columnCount) + i] = value; + } + } + return this; + } } // AbstractCursor implementation. diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java index b6c260fd3e023f87f5b84abaa6614310441de8b5..2d47f289b38efa676922d710229465d992089c39 100644 --- a/core/java/android/gesture/GestureOverlayView.java +++ b/core/java/android/gesture/GestureOverlayView.java @@ -486,6 +486,7 @@ public class GestureOverlayView extends FrameLayout { @Override protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); cancelClearAnimation(); } diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 4e510809bf5e403751326793e5ff4715a36d5b7d..feb47aa6d38106e35692dde460edfa8c315d03df 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -31,6 +31,11 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.RSIllegalArgumentException; +import android.renderscript.Type; import android.util.Log; import android.text.TextUtils; import android.view.Surface; @@ -152,6 +157,7 @@ public class Camera { private PictureCallback mRawImageCallback; private PictureCallback mJpegCallback; private PreviewCallback mPreviewCallback; + private boolean mUsingPreviewAllocation; private PictureCallback mPostviewCallback; private AutoFocusCallback mAutoFocusCallback; private AutoFocusMoveCallback mAutoFocusMoveCallback; @@ -327,6 +333,7 @@ public class Camera { mJpegCallback = null; mPreviewCallback = null; mPostviewCallback = null; + mUsingPreviewAllocation = false; mZoomListener = null; Looper looper; @@ -587,6 +594,9 @@ public class Camera { mPreviewCallback = cb; mOneShot = false; mWithBuffer = false; + if (cb != null) { + mUsingPreviewAllocation = false; + } // Always use one-shot mode. We fake camera preview mode by // doing one-shot preview continuously. setHasPreviewCallback(cb != null, false); @@ -610,6 +620,9 @@ public class Camera { mPreviewCallback = cb; mOneShot = true; mWithBuffer = false; + if (cb != null) { + mUsingPreviewAllocation = false; + } setHasPreviewCallback(cb != null, false); } @@ -645,6 +658,9 @@ public class Camera { mPreviewCallback = cb; mOneShot = false; mWithBuffer = true; + if (cb != null) { + mUsingPreviewAllocation = false; + } setHasPreviewCallback(cb != null, true); } @@ -744,6 +760,134 @@ public class Camera { private native final void _addCallbackBuffer( byte[] callbackBuffer, int msgType); + /** + *

    Create a {@link android.renderscript RenderScript} + * {@link android.renderscript.Allocation Allocation} to use as a + * destination of preview callback frames. Use + * {@link #setPreviewCallbackAllocation setPreviewCallbackAllocation} to use + * the created Allocation as a destination for camera preview frames.

    + * + *

    The Allocation will be created with a YUV type, and its contents must + * be accessed within Renderscript with the {@code rsGetElementAtYuv_*} + * accessor methods. Its size will be based on the current + * {@link Parameters#getPreviewSize preview size} configured for this + * camera.

    + * + * @param rs the RenderScript context for this Allocation. + * @param usage additional usage flags to set for the Allocation. The usage + * flag {@link android.renderscript.Allocation#USAGE_IO_INPUT} will always + * be set on the created Allocation, but additional flags may be provided + * here. + * @return a new YUV-type Allocation with dimensions equal to the current + * preview size. + * @throws RSIllegalArgumentException if the usage flags are not compatible + * with an YUV Allocation. + * @see #setPreviewCallbackAllocation + * @hide + */ + public final Allocation createPreviewAllocation(RenderScript rs, int usage) + throws RSIllegalArgumentException { + Parameters p = getParameters(); + Size previewSize = p.getPreviewSize(); + Type.Builder yuvBuilder = new Type.Builder(rs, + Element.createPixel(rs, + Element.DataType.UNSIGNED_8, + Element.DataKind.PIXEL_YUV)); + // Use YV12 for wide compatibility. Changing this requires also + // adjusting camera service's format selection. + yuvBuilder.setYuvFormat(ImageFormat.YV12); + yuvBuilder.setX(previewSize.width); + yuvBuilder.setY(previewSize.height); + + Allocation a = Allocation.createTyped(rs, yuvBuilder.create(), + usage | Allocation.USAGE_IO_INPUT); + + return a; + } + + /** + *

    Set an {@link android.renderscript.Allocation Allocation} as the + * target of preview callback data. Use this method for efficient processing + * of camera preview data with RenderScript. The Allocation must be created + * with the {@link #createPreviewAllocation createPreviewAllocation } + * method.

    + * + *

    Setting a preview allocation will disable any active preview callbacks + * set by {@link #setPreviewCallback setPreviewCallback} or + * {@link #setPreviewCallbackWithBuffer setPreviewCallbackWithBuffer}, and + * vice versa. Using a preview allocation still requires an active standard + * preview target to be set, either with + * {@link #setPreviewTexture setPreviewTexture} or + * {@link #setPreviewDisplay setPreviewDisplay}.

    + * + *

    To be notified when new frames are available to the Allocation, use + * {@link android.renderscript.Allocation#setIoInputNotificationHandler Allocation.setIoInputNotificationHandler}. To + * update the frame currently accessible from the Allocation to the latest + * preview frame, call + * {@link android.renderscript.Allocation#ioReceive Allocation.ioReceive}.

    + * + *

    To disable preview into the Allocation, call this method with a + * {@code null} parameter.

    + * + *

    Once a preview allocation is set, the preview size set by + * {@link Parameters#setPreviewSize setPreviewSize} cannot be changed. If + * you wish to change the preview size, first remove the preview allocation + * by calling {@code setPreviewCallbackAllocation(null)}, then change the + * preview size, create a new preview Allocation with + * {@link #createPreviewAllocation createPreviewAllocation}, and set it as + * the new preview callback allocation target.

    + * + *

    If you are using the preview data to create video or still images, + * strongly consider using {@link android.media.MediaActionSound} to + * properly indicate image capture or recording start/stop to the user.

    + * + * @param previewAllocation the allocation to use as destination for preview + * @throws IOException if configuring the camera to use the Allocation for + * preview fails. + * @throws IllegalArgumentException if the Allocation's dimensions or other + * parameters don't meet the requirements. + * @see #createPreviewAllocation + * @see #setPreviewCallback + * @see #setPreviewCallbackWithBuffer + * @hide + */ + public final void setPreviewCallbackAllocation(Allocation previewAllocation) + throws IOException { + Surface previewSurface = null; + if (previewAllocation != null) { + Parameters p = getParameters(); + Size previewSize = p.getPreviewSize(); + if (previewSize.width != previewAllocation.getType().getX() || + previewSize.height != previewAllocation.getType().getY()) { + throw new IllegalArgumentException( + "Allocation dimensions don't match preview dimensions: " + + "Allocation is " + + previewAllocation.getType().getX() + + ", " + + previewAllocation.getType().getY() + + ". Preview is " + previewSize.width + ", " + + previewSize.height); + } + if ((previewAllocation.getUsage() & + Allocation.USAGE_IO_INPUT) == 0) { + throw new IllegalArgumentException( + "Allocation usage does not include USAGE_IO_INPUT"); + } + if (previewAllocation.getType().getElement().getDataKind() != + Element.DataKind.PIXEL_YUV) { + throw new IllegalArgumentException( + "Allocation is not of a YUV type"); + } + previewSurface = previewAllocation.getSurface(); + mUsingPreviewAllocation = true; + } else { + mUsingPreviewAllocation = false; + } + setPreviewCallbackSurface(previewSurface); + } + + private native final void setPreviewCallbackSurface(Surface s); + private class EventHandler extends Handler { private Camera mCamera; @@ -1492,6 +1636,17 @@ public class Camera { * @see #getParameters() */ public void setParameters(Parameters params) { + // If using preview allocations, don't allow preview size changes + if (mUsingPreviewAllocation) { + Size newPreviewSize = params.getPreviewSize(); + Size currentPreviewSize = getParameters().getPreviewSize(); + if (newPreviewSize.width != currentPreviewSize.width || + newPreviewSize.height != currentPreviewSize.height) { + throw new IllegalStateException("Cannot change preview size" + + " while a preview allocation is configured."); + } + } + native_setParameters(params.flatten()); } @@ -2614,21 +2769,24 @@ public class Camera { * JPEG {@link PictureCallback}. The camera driver may set orientation * in the EXIF header without rotating the picture. Or the driver may * rotate the picture and the EXIF thumbnail. If the Jpeg picture is - * rotated, the orientation in the EXIF header will be missing or 1 - * (row #0 is top and column #0 is left side). - * - *

    If applications want to rotate the picture to match the orientation - * of what users see, apps should use {@link - * android.view.OrientationEventListener} and {@link CameraInfo}. - * The value from OrientationEventListener is relative to the natural - * orientation of the device. CameraInfo.orientation is the angle - * between camera orientation and natural device orientation. The sum - * of the two is the rotation angle for back-facing camera. The - * difference of the two is the rotation angle for front-facing camera. - * Note that the JPEG pictures of front-facing cameras are not mirrored - * as in preview display. - * - *

    For example, suppose the natural orientation of the device is + * rotated, the orientation in the EXIF header will be missing or 1 (row + * #0 is top and column #0 is left side). + * + *

    + * If applications want to rotate the picture to match the orientation + * of what users see, apps should use + * {@link android.view.OrientationEventListener} and + * {@link android.hardware.Camera.CameraInfo}. The value from + * OrientationEventListener is relative to the natural orientation of + * the device. CameraInfo.orientation is the angle between camera + * orientation and natural device orientation. The sum of the two is the + * rotation angle for back-facing camera. The difference of the two is + * the rotation angle for front-facing camera. Note that the JPEG + * pictures of front-facing cameras are not mirrored as in preview + * display. + * + *

    + * For example, suppose the natural orientation of the device is * portrait. The device is rotated 270 degrees clockwise, so the device * orientation is 270. Suppose a back-facing camera sensor is mounted in * landscape and the top side of the camera sensor is aligned with the diff --git a/core/java/android/hardware/CameraInfo.aidl b/core/java/android/hardware/CameraInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e21e6941e9764b3b701b5b1e7d08be72c78ec787 --- /dev/null +++ b/core/java/android/hardware/CameraInfo.aidl @@ -0,0 +1,20 @@ +/* + * 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.hardware; + +/** @hide */ +parcelable CameraInfo; diff --git a/core/java/android/hardware/CameraInfo.java b/core/java/android/hardware/CameraInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..53da0ce00e399c3e0a5e9e8572c7e0eca20a9529 --- /dev/null +++ b/core/java/android/hardware/CameraInfo.java @@ -0,0 +1,63 @@ +/* + * 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.hardware; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Information about a camera + * + * @hide + */ +public class CameraInfo implements Parcelable { + // Can't parcel nested classes, so make this a top level class that composes + // CameraInfo. + public Camera.CameraInfo info = new Camera.CameraInfo(); + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(info.facing); + out.writeInt(info.orientation); + } + + public void readFromParcel(Parcel in) { + info.facing = in.readInt(); + info.orientation = in.readInt(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public CameraInfo createFromParcel(Parcel in) { + CameraInfo info = new CameraInfo(); + info.readFromParcel(in); + + return info; + } + + @Override + public CameraInfo[] newArray(int size) { + return new CameraInfo[size]; + } + }; +}; diff --git a/core/java/android/hardware/ConsumerIrManager.java b/core/java/android/hardware/ConsumerIrManager.java new file mode 100644 index 0000000000000000000000000000000000000000..770878149ebce2ca69d23396afd348e971e33770 --- /dev/null +++ b/core/java/android/hardware/ConsumerIrManager.java @@ -0,0 +1,156 @@ +/* + * 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.hardware; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +/** + * Class that operates consumer infrared on the device. + * + *

    + * To obtain an instance of the system infrared transmitter, call + * {@link android.content.Context#getSystemService(java.lang.String) + * Context.getSystemService()} with + * {@link android.content.Context#CONSUMER_IR_SERVICE} as the argument. + *

    + */ +public final class ConsumerIrManager { + private static final String TAG = "ConsumerIr"; + + private final String mPackageName; + private final IConsumerIrService mService; + + /** + * @hide to prevent subclassing from outside of the framework + */ + public ConsumerIrManager(Context context) { + mPackageName = context.getPackageName(); + mService = IConsumerIrService.Stub.asInterface( + ServiceManager.getService(Context.CONSUMER_IR_SERVICE)); + } + + /** + * Check whether the device has an infrared emitter. + * + * @return true if the device has an infrared emitter, else false. + */ + public boolean hasIrEmitter() { + if (mService == null) { + Log.w(TAG, "no consumer ir service."); + return false; + } + + try { + return mService.hasIrEmitter(); + } catch (RemoteException e) { + } + return false; + } + + /** + * Tansmit and infrared pattern + *

    + * This method is synchronous; when it returns the pattern has + * been transmitted. Only patterns shorter than 2 seconds will + * be transmitted. + *

    + * + * @param carrierFrequency The IR carrier frequency in Hertz. + * @param pattern The alternating on/off pattern in microseconds to transmit. + */ + public void transmit(int carrierFrequency, int[] pattern) { + if (mService == null) { + Log.w(TAG, "failed to transmit; no consumer ir service."); + return; + } + + try { + mService.transmit(mPackageName, carrierFrequency, pattern); + } catch (RemoteException e) { + Log.w(TAG, "failed to transmit.", e); + } + } + + /** + * Represents a range of carrier frequencies (inclusive) on which the + * infrared transmitter can transmit + */ + public final class CarrierFrequencyRange { + private final int mMinFrequency; + private final int mMaxFrequency; + + /** + * Create a segment of a carrier frequency range. + * + * @param min The minimum transmittable frequency in this range segment. + * @param max The maximum transmittable frequency in this range segment. + */ + public CarrierFrequencyRange(int min, int max) { + mMinFrequency = min; + mMaxFrequency = max; + } + + /** + * Get the minimum (inclusive) frequency in this range segment. + */ + public int getMinFrequency() { + return mMinFrequency; + } + + /** + * Get the maximum (inclusive) frequency in this range segment. + */ + public int getMaxFrequency() { + return mMaxFrequency; + } + }; + + /** + * Query the infrared transmitter's supported carrier frequencies + * + * @return an array of + * {@link android.hardware.ConsumerIrManager.CarrierFrequencyRange} + * objects representing the ranges that the transmitter can support, or + * null if there was an error communicating with the Consumer IR Service. + */ + public CarrierFrequencyRange[] getCarrierFrequencies() { + if (mService == null) { + Log.w(TAG, "no consumer ir service."); + return null; + } + + try { + int[] freqs = mService.getCarrierFrequencies(); + if (freqs.length % 2 != 0) { + Log.w(TAG, "consumer ir service returned an uneven number of frequencies."); + return null; + } + CarrierFrequencyRange[] range = new CarrierFrequencyRange[freqs.length / 2]; + + for (int i = 0; i < freqs.length; i += 2) { + range[i / 2] = new CarrierFrequencyRange(freqs[i], freqs[i+1]); + } + return range; + } catch (RemoteException e) { + } + return null; + } + +} diff --git a/core/java/android/hardware/ICamera.aidl b/core/java/android/hardware/ICamera.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d4f64f8468b3c8d4321096381bc90cbba039d2de --- /dev/null +++ b/core/java/android/hardware/ICamera.aidl @@ -0,0 +1,26 @@ +/* + * 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.hardware; + +/** @hide */ +interface ICamera +{ + /** + * Keep up-to-date with frameworks/av/include/camera/ICamera.h + */ + void disconnect(); +} diff --git a/core/java/android/hardware/ICameraClient.aidl b/core/java/android/hardware/ICameraClient.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d7877b4be956f439c86dea4e5e9e05c0ece8a636 --- /dev/null +++ b/core/java/android/hardware/ICameraClient.aidl @@ -0,0 +1,26 @@ +/* + * 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.hardware; + +/** @hide */ +interface ICameraClient +{ + /** + * Keep up-to-date with frameworks/av/include/camera/ICameraClient.h + */ + // TODO: consider implementing this. +} diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..542af6ac53f0fc1e7cbaa7abd1f53300f242d991 --- /dev/null +++ b/core/java/android/hardware/ICameraService.aidl @@ -0,0 +1,64 @@ +/* + * 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.hardware; + +import android.hardware.ICamera; +import android.hardware.ICameraClient; +import android.hardware.IProCameraUser; +import android.hardware.IProCameraCallbacks; +import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.BinderHolder; +import android.hardware.ICameraServiceListener; +import android.hardware.CameraInfo; + +/** @hide */ +interface ICameraService +{ + /** + * Keep up-to-date with frameworks/av/include/camera/ICameraService.h + */ + int getNumberOfCameras(); + + // rest of 'int' return values in this file are actually status_t + + int getCameraInfo(int cameraId, out CameraInfo info); + + int connect(ICameraClient client, int cameraId, + String clientPackageName, + int clientUid, + // Container for an ICamera object + out BinderHolder device); + + int connectPro(IProCameraCallbacks callbacks, int cameraId, + String clientPackageName, + int clientUid, + // Container for an IProCameraUser object + out BinderHolder device); + + int connectDevice(ICameraDeviceCallbacks callbacks, int cameraId, + String clientPackageName, + int clientUid, + // Container for an ICameraDeviceUser object + out BinderHolder device); + + int addListener(ICameraServiceListener listener); + int removeListener(ICameraServiceListener listener); + + int getCameraCharacteristics(int cameraId, out CameraMetadataNative info); +} diff --git a/core/java/android/hardware/ICameraServiceListener.aidl b/core/java/android/hardware/ICameraServiceListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c548496525ea27a8182ddb21469074738af24702 --- /dev/null +++ b/core/java/android/hardware/ICameraServiceListener.aidl @@ -0,0 +1,26 @@ +/* + * 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.hardware; + +/** @hide */ +interface ICameraServiceListener +{ + /** + * Keep up-to-date with frameworks/av/include/camera/ICameraServiceListener.h + */ + void onStatusChanged(int status, int cameraId); +} diff --git a/core/java/android/hardware/IConsumerIrService.aidl b/core/java/android/hardware/IConsumerIrService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c79bd19582267007e13e1b54780e49a52a669fec --- /dev/null +++ b/core/java/android/hardware/IConsumerIrService.aidl @@ -0,0 +1,26 @@ +/** + * 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.hardware; + +/** {@hide} */ +interface IConsumerIrService +{ + boolean hasIrEmitter(); + void transmit(String packageName, int carrierFrequency, in int[] pattern); + int[] getCarrierFrequencies(); +} + diff --git a/core/java/android/hardware/IProCameraCallbacks.aidl b/core/java/android/hardware/IProCameraCallbacks.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a09b452176bdb00cfe26366f5beca7a3adc9f661 --- /dev/null +++ b/core/java/android/hardware/IProCameraCallbacks.aidl @@ -0,0 +1,26 @@ +/* + * 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.hardware; + +/** @hide */ +interface IProCameraCallbacks +{ + /** + * Keep up-to-date with frameworks/av/include/camera/IProCameraCallbacks.h + */ + // TODO: consider implementing this. +} diff --git a/core/java/android/hardware/IProCameraUser.aidl b/core/java/android/hardware/IProCameraUser.aidl new file mode 100644 index 0000000000000000000000000000000000000000..eacb0f48a91ea5d47a5f12d516ece397b54365c7 --- /dev/null +++ b/core/java/android/hardware/IProCameraUser.aidl @@ -0,0 +1,26 @@ +/* + * 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.hardware; + +/** @hide */ +interface IProCameraUser +{ + /** + * Keep up-to-date with frameworks/av/include/camera/IProCameraUser.h + */ + void disconnect(); +} diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 5cc11505d6b9c4cbb950f24e624846f0e9bba9dc..89a58196626556f19adbf990dc3956a1ce50803c 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -129,7 +129,7 @@ public final class Sensor { * due to distortions that arise from magnetized iron, steel or permanent magnets on the * device) is not considered in the given sensor values. However, such hard iron bias values * are returned to you separately in the result {@link android.hardware.SensorEvent#values} - * so you may use them for custom calibrations. + * so you may use them for custom calibrations. *

    Also, no periodic calibration is performed * (i.e. there are no discontinuities in the data stream while using this sensor) and * assumptions that the magnetic field is due to the Earth's poles is avoided, but @@ -174,7 +174,7 @@ public final class Sensor { public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; /** - * A constant describing the significant motion trigger sensor. + * A constant describing a significant motion trigger sensor. *

    * It triggers when an event occurs and then automatically disables * itself. The sensor continues to operate while the device is asleep @@ -185,6 +185,42 @@ public final class Sensor { */ public static final int TYPE_SIGNIFICANT_MOTION = 17; + /** + * A constant describing a step detector sensor. + *

    + * A sensor of this type triggers an event each time a step is taken by the user. The only + * allowed value to return is 1.0 and an event is generated for each step. Like with any other + * event, the timestamp indicates when the event (here the step) occurred, this corresponds to + * when the foot hit the ground, generating a high variation in acceleration. + *

    + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_STEP_DETECTOR = 18; + + /** + * A constant describing a step counter sensor. + *

    + * A sensor of this type returns the number of steps taken by the user since the last reboot + * while activated. The value is returned as a float (with the fractional part set to zero) and + * is reset to zero only on a system reboot. The timestamp of the event is set to the time when + * the first step for that event was taken. This sensor is implemented in hardware and is + * expected to be low power. + *

    + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_STEP_COUNTER = 19; + + /** + * A constant describing the geo-magnetic rotation vector. + *

    + * Similar to {@link #TYPE_ROTATION_VECTOR}, but using a magnetometer instead of using a + * gyroscope. This sensor uses lower power than the other rotation vectors, because it doesn't + * use the gyroscope. However, it is more noisy and will work best outdoors. + *

    + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; + /** * A constant describing all sensor types. */ @@ -204,37 +240,71 @@ public final class Sensor { // TODO(): The following arrays are fragile and error-prone. This needs to be refactored. // Note: This needs to be updated, whenever a new sensor is added. - private static int[] sSensorReportingModes = { - REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, - REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS, - REPORTING_MODE_ON_CHANGE, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS, - REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE, - REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, - REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ONE_SHOT }; - - // Note: This needs to be updated, whenever a new sensor is added. - // Holds the maximum length of the values array associated with {@link SensorEvent} or - // {@link TriggerEvent} for the Sensor - private static int[] sMaxLengthValuesArray = { - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, - 6, 4, 6, 1 }; + // Holds the reporting mode and maximum length of the values array + // associated with + // {@link SensorEvent} or {@link TriggerEvent} for the Sensor + private static final int[] sSensorReportingModes = { + 0, 0, // padding because sensor types start at 1 + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_ACCELEROMETER + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GEOMAGNETIC_FIELD + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_ORIENTATION + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GYROSCOPE + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_LIGHT + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_PRESSURE + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_TEMPERATURE + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_PROXIMITY + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GRAVITY + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_LINEAR_ACCELERATION + REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_ROTATION_VECTOR + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_RELATIVE_HUMIDITY + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_AMBIENT_TEMPERATURE + REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED + REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_GAME_ROTATION_VECTOR + REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_GYROSCOPE_UNCALIBRATED + REPORTING_MODE_ONE_SHOT, 1, // SENSOR_TYPE_SIGNIFICANT_MOTION + // added post 4.3 + REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_DETECTOR + REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_COUNTER + REPORTING_MODE_CONTINUOUS, 5 // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR + }; static int getReportingMode(Sensor sensor) { - // mType starts from offset 1. - return sSensorReportingModes[sensor.mType - 1]; + int offset = sensor.mType * 2; + if (offset >= sSensorReportingModes.length) { + // we don't know about this sensor, so this is probably a + // vendor-defined sensor, in that case, we figure out the reporting + // mode from the sensor meta-data. + int minDelay = sensor.mMinDelay; + if (minDelay == 0) { + return REPORTING_MODE_ON_CHANGE; + } else if (minDelay < 0) { + return REPORTING_MODE_ONE_SHOT; + } else { + return REPORTING_MODE_CONTINUOUS; + } + } + return sSensorReportingModes[offset]; } static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) { - // mType starts from offset 1. - int len = sMaxLengthValuesArray[sensor.mType - 1]; - + int type = sensor.mType; // RotationVector length has changed to 3 to 5 for API level 18 // Set it to 3 for backward compatibility. - if (sensor.getType() == Sensor.TYPE_ROTATION_VECTOR && + if (type == Sensor.TYPE_ROTATION_VECTOR && sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) { - len = 3; + return 3; + } + int offset = type * 2 + 1; + if (offset >= sSensorReportingModes.length) { + // we don't know about this sensor, so this is probably a + // vendor-defined sensor, in that case, we don't know how many value + // it has + // so we return the maximum and assume the app will know. + // FIXME: sensor HAL should advertise how much data is returned per + // sensor + return 16; } - return len; + return sSensorReportingModes[offset]; } /* Some of these fields are set only by the native bindings in @@ -249,6 +319,8 @@ public final class Sensor { private float mResolution; private float mPower; private int mMinDelay; + private int mFifoReservedEventCount; + private int mFifoMaxEventCount; Sensor() { } @@ -311,6 +383,24 @@ public final class Sensor { return mMinDelay; } + /** + * @return Number of events reserved for this sensor in the batch mode FIFO. This gives a + * guarantee on the minimum number of events that can be batched. + */ + public int getFifoReservedEventCount() { + return mFifoReservedEventCount; + } + + /** + * @return Maximum number of events of this sensor that could be batched. If this value is zero + * it indicates that batch mode is not supported for this sensor. If other applications + * registered to batched sensors, the actual number of events that can be batched might be + * smaller because the hardware FiFo will be partially used to batch the other sensors. + */ + public int getFifoMaxEventCount() { + return mFifoMaxEventCount; + } + /** @hide */ public int getHandle() { return mHandle; diff --git a/core/java/android/hardware/SensorEventListener2.java b/core/java/android/hardware/SensorEventListener2.java new file mode 100644 index 0000000000000000000000000000000000000000..4c3b42908b012cb96b0cb1c608f2a87e75e3cdbe --- /dev/null +++ b/core/java/android/hardware/SensorEventListener2.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware; + +/** + * Used for receiving a notification when a flush() has been successfully completed. + */ +public interface SensorEventListener2 extends SensorEventListener { + /** + * Called after flush() is completed. This flush() could have been initiated by this application + * or some other application. All the events in the batch at the point when the flush was called + * have been delivered to the applications registered for those sensor events. + *

    + * + * @param sensor The {@link android.hardware.Sensor Sensor} on which flush was called. + * + * @see android.hardware.SensorManager#flush(SensorEventListener) + */ + public void onFlushCompleted(Sensor sensor); +} diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index 30118f9dd348b9c568c2f3ecee85d9b51c179f0d..b931313083166bffd563ff4bde1e5ec72b26da40 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -533,8 +533,6 @@ public abstract class SensorManager { * * @see #unregisterListener(SensorEventListener) * @see #registerListener(SensorEventListener, Sensor, int) - * - * @throws IllegalArgumentException when sensor is a trigger sensor. */ public void unregisterListener(SensorEventListener listener, Sensor sensor) { if (listener == null || sensor == null) { @@ -582,7 +580,7 @@ public abstract class SensorManager { * @param sensor * The {@link android.hardware.Sensor Sensor} to register to. * - * @param rate + * @param rateUs * The rate {@link android.hardware.SensorEvent sensor events} are * delivered at. This is only a hint to the system. Events may be * received faster or slower than the specified rate. Usually events @@ -601,15 +599,74 @@ public abstract class SensorManager { * @see #unregisterListener(SensorEventListener) * @see #unregisterListener(SensorEventListener, Sensor) * - * @throws IllegalArgumentException when sensor is null or a trigger sensor */ - public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) { - return registerListener(listener, sensor, rate, null); + public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs) { + return registerListener(listener, sensor, rateUs, null); } /** - * Registers a {@link android.hardware.SensorEventListener - * SensorEventListener} for the given sensor. + * Enables batch mode for a sensor with the given rate and maxBatchReportLatency. If the + * underlying hardware does not support batch mode, this defaults to + * {@link #registerListener(SensorEventListener, Sensor, int)} and other parameters are + * ignored. In non-batch mode, all sensor events must be reported as soon as they are detected. + * While in batch mode, sensor events do not need to be reported as soon as they are detected. + * They can be temporarily stored in batches and reported in batches, as long as no event is + * delayed by more than "maxBatchReportLatency" microseconds. That is, all events since the + * previous batch are recorded and returned all at once. This allows to reduce the amount of + * interrupts sent to the SoC, and allows the SoC to switch to a lower power state (Idle) while + * the sensor is capturing and batching data. + *

    + * Registering to a sensor in batch mode will not prevent the SoC from going to suspend mode. In + * this case, the sensor will continue to gather events and store it in a hardware FIFO. If the + * FIFO gets full before the AP wakes up again, some events will be lost, as the older events + * get overwritten by new events in the hardware FIFO. This can be avoided by holding a wake + * lock. If the application holds a wake lock, the SoC will not go to suspend mode, so no events + * will be lost, as the events will be reported before the FIFO gets full. + *

    + *

    + * Batching is always best effort. If a different application requests updates in continuous + * mode, this application will also get events in continuous mode. Batch mode updates can be + * unregistered by calling {@link #unregisterListener(SensorEventListener)}. + *

    + *

    + *

    + * Note: Don't use this method with a one shot trigger sensor such as + * {@link Sensor#TYPE_SIGNIFICANT_MOTION}. Use + * {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.

    + * + * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object + * that will receive the sensor events. If the application is interested in receiving + * flush complete notifications, it should register with + * {@link android.hardware.SensorEventListener SensorEventListener2} instead. + * @param sensor The {@link android.hardware.Sensor Sensor} to register to. + * @param rateUs The desired delay between two consecutive events in microseconds. This is only + * a hint to the system. Events may be received faster or slower than the specified + * rate. Usually events are received faster. Can be one of + * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, + * {@link #SENSOR_DELAY_GAME}, {@link #SENSOR_DELAY_FASTEST} or the delay in + * microseconds. + * @param maxBatchReportLatencyUs An event in the batch can be delayed by at most + * maxBatchReportLatency microseconds. More events can be batched if this value is + * large. If this is set to zero, batch mode is disabled and events are delivered in + * continuous mode as soon as they are available which is equivalent to calling + * {@link #registerListener(SensorEventListener, Sensor, int)}. + * @return true if batch mode is successfully enabled for this sensor, + * false otherwise. + * @see #registerListener(SensorEventListener, Sensor, int) + * @see #unregisterListener(SensorEventListener) + * @see #flush(SensorEventListener) + */ + public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs, + int maxBatchReportLatencyUs) { + int delay = getDelay(rateUs); + return registerListenerImpl(listener, sensor, delay, null, maxBatchReportLatencyUs, 0); + } + + /** + * Registers a {@link android.hardware.SensorEventListener SensorEventListener} for the given + * sensor. Events are delivered in continuous mode as soon as they are available. To reduce the + * battery usage, use {@link #registerListener(SensorEventListener, Sensor, int, int)} which + * enables batch mode for the sensor. * *

    * Note: Don't use this method with a one shot trigger sensor such as @@ -624,7 +681,7 @@ public abstract class SensorManager { * @param sensor * The {@link android.hardware.Sensor Sensor} to register to. * - * @param rate + * @param rateUs * The rate {@link android.hardware.SensorEvent sensor events} are * delivered at. This is only a hint to the system. Events may be * received faster or slower than the specified rate. Usually events @@ -641,45 +698,80 @@ public abstract class SensorManager { * {@link android.hardware.SensorEvent sensor events} will be * delivered to. * - * @return true if the sensor is supported and successfully enabled. + * @return true if the sensor is supported and successfully enabled. * * @see #registerListener(SensorEventListener, Sensor, int) * @see #unregisterListener(SensorEventListener) * @see #unregisterListener(SensorEventListener, Sensor) - * - * @throws IllegalArgumentException when sensor is null or a trigger sensor */ - public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate, + public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs, Handler handler) { - if (listener == null || sensor == null) { - return false; - } - - int delay = -1; - switch (rate) { - case SENSOR_DELAY_FASTEST: - delay = 0; - break; - case SENSOR_DELAY_GAME: - delay = 20000; - break; - case SENSOR_DELAY_UI: - delay = 66667; - break; - case SENSOR_DELAY_NORMAL: - delay = 200000; - break; - default: - delay = rate; - break; - } + int delay = getDelay(rateUs); + return registerListenerImpl(listener, sensor, delay, handler, 0, 0); + } - return registerListenerImpl(listener, sensor, delay, handler); + /** + * Enables batch mode for a sensor with the given rate and maxBatchReportLatency. + * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object + * that will receive the sensor events. If the application is interested in receiving + * flush complete notifications, it should register with + * {@link android.hardware.SensorEventListener SensorEventListener2} instead. + * @param sensor The {@link android.hardware.Sensor Sensor} to register to. + * @param rateUs The desired delay between two consecutive events in microseconds. This is only + * a hint to the system. Events may be received faster or slower than the specified + * rate. Usually events are received faster. Can be one of + * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, + * {@link #SENSOR_DELAY_GAME}, {@link #SENSOR_DELAY_FASTEST} or the delay in + * microseconds. + * @param maxBatchReportLatencyUs An event in the batch can be delayed by at most + * maxBatchReportLatency microseconds. More events can be batched if this value is + * large. If this is set to zero, batch mode is disabled and events are delivered in + * continuous mode as soon as they are available which is equivalent to calling + * {@link #registerListener(SensorEventListener, Sensor, int)}. + * @param handler The {@link android.os.Handler Handler} the + * {@link android.hardware.SensorEvent sensor events} will be delivered to. + * + * @return true if batch mode is successfully enabled for this sensor, + * false otherwise. + * @see #registerListener(SensorEventListener, Sensor, int, int) + */ + public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs, + int maxBatchReportLatencyUs, Handler handler) { + int delayUs = getDelay(rateUs); + return registerListenerImpl(listener, sensor, delayUs, handler, maxBatchReportLatencyUs, 0); } /** @hide */ protected abstract boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, - int delay, Handler handler); + int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags); + + + /** + * Flushes the batch FIFO of all the sensors registered for this listener. If there are events + * in the FIFO of the sensor, they are returned as if the batch timeout in the FIFO of the + * sensors had expired. Events are returned in the usual way through the SensorEventListener. + * This call doesn't affect the batch timeout for this sensor. This call is asynchronous and + * returns immediately. + * {@link android.hardware.SensorEventListener2#onFlushCompleted onFlushCompleted} is called + * after all the events in the batch at the time of calling this method have been delivered + * successfully. If the hardware doesn't support flush, it still returns true and a trivial + * flush complete event is sent after the current event for all the clients registered for this + * sensor. + * + * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object + * which was previously used in a registerListener call. + * @return true if the flush is initiated successfully on all the sensors + * registered for this listener, false if no sensor is previously registered for this + * listener or flush on one of the sensors fails. + * @see #registerListener(SensorEventListener, Sensor, int, int) + * @throws IllegalArgumentException when listener is null. + */ + public boolean flush(SensorEventListener listener) { + return flushImpl(listener); + } + + /** @hide */ + protected abstract boolean flushImpl(SensorEventListener listener); /** *

    @@ -1079,15 +1171,15 @@ public abstract class SensorManager { *

    * All three angles above are in radians and positive in the * counter-clockwise direction. - * + * * @param R * rotation matrix see {@link #getRotationMatrix}. - * + * * @param values * an array of 3 floats to hold the result. - * + * * @return The array values passed as argument. - * + * * @see #getRotationMatrix(float[], float[], float[], float[]) * @see GeomagneticField */ @@ -1407,4 +1499,26 @@ public abstract class SensorManager { return mLegacySensorManager; } } + + private static int getDelay(int rate) { + int delay = -1; + switch (rate) { + case SENSOR_DELAY_FASTEST: + delay = 0; + break; + case SENSOR_DELAY_GAME: + delay = 20000; + break; + case SENSOR_DELAY_UI: + delay = 66667; + break; + case SENSOR_DELAY_NORMAL: + delay = 200000; + break; + default: + delay = rate; + break; + } + return delay; + } } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 852cf4aba49e2d7aeea7441f270ad5f46eb5a1b5..14f67c58289432d561117d9d2e61644c3e35f101 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -93,30 +93,38 @@ public class SystemSensorManager extends SensorManager { /** @hide */ @Override protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, - int delay, Handler handler) - { + int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) { + if (listener == null || sensor == null) { + Log.e(TAG, "sensor or listener is null"); + return false; + } + // Trigger Sensors should use the requestTriggerSensor call. + if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) { + Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor."); + return false; + } + if (maxBatchReportLatencyUs < 0 || delayUs < 0) { + Log.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative"); + return false; + } + // Invariants to preserve: // - one Looper per SensorEventListener // - one Looper per SensorEventQueue // We map SensorEventListener to a SensorEventQueue, which holds the looper - if (sensor == null) throw new IllegalArgumentException("sensor cannot be null"); - - // Trigger Sensors should use the requestTriggerSensor call. - if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) return false; - synchronized (mSensorListeners) { SensorEventQueue queue = mSensorListeners.get(listener); if (queue == null) { Looper looper = (handler != null) ? handler.getLooper() : mMainLooper; queue = new SensorEventQueue(listener, looper, this); - if (!queue.addSensor(sensor, delay)) { + if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags)) { queue.dispose(); return false; } mSensorListeners.put(listener, queue); return true; } else { - return queue.addSensor(sensor, delay); + return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags); } } } @@ -157,14 +165,14 @@ public class SystemSensorManager extends SensorManager { TriggerEventQueue queue = mTriggerListeners.get(listener); if (queue == null) { queue = new TriggerEventQueue(listener, mMainLooper, this); - if (!queue.addSensor(sensor, 0)) { + if (!queue.addSensor(sensor, 0, 0, 0)) { queue.dispose(); return false; } mTriggerListeners.put(listener, queue); return true; } else { - return queue.addSensor(sensor, 0); + return queue.addSensor(sensor, 0, 0, 0); } } } @@ -195,6 +203,19 @@ public class SystemSensorManager extends SensorManager { } } + protected boolean flushImpl(SensorEventListener listener) { + if (listener == null) throw new IllegalArgumentException("listener cannot be null"); + + synchronized (mSensorListeners) { + SensorEventQueue queue = mSensorListeners.get(listener); + if (queue == null) { + return false; + } else { + return (queue.flush() == 0); + } + } + } + /* * BaseEventQueue is the communication channel with the sensor service, * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between @@ -202,11 +223,12 @@ public class SystemSensorManager extends SensorManager { */ private static abstract class BaseEventQueue { private native int nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ, - float[] scratch); - private static native int nativeEnableSensor(int eventQ, int handle, int us); + private static native int nativeEnableSensor(int eventQ, int handle, int rateUs, + int maxBatchReportLatencyUs, int reservedFlags); private static native int nativeDisableSensor(int eventQ, int handle); private static native void nativeDestroySensorEventQueue(int eventQ); + private static native int nativeFlushSensor(int eventQ); private int nSensorEventQueue; private final SparseBooleanArray mActiveSensors = new SparseBooleanArray(); protected final SparseIntArray mSensorAccuracies = new SparseIntArray(); @@ -225,7 +247,8 @@ public class SystemSensorManager extends SensorManager { dispose(false); } - public boolean addSensor(Sensor sensor, int delay) { + public boolean addSensor( + Sensor sensor, int delayUs, int maxBatchReportLatencyUs, int reservedFlags) { // Check if already present. int handle = sensor.getHandle(); if (mActiveSensors.get(handle)) return false; @@ -233,9 +256,13 @@ public class SystemSensorManager extends SensorManager { // Get ready to receive events before calling enable. mActiveSensors.put(handle, true); addSensorEvent(sensor); - if (enableSensor(sensor, delay) != 0) { - removeSensor(sensor, false); - return false; + if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags) != 0) { + // Try continuous mode if batching fails. + if (maxBatchReportLatencyUs == 0 || + maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0, 0) != 0) { + removeSensor(sensor, false); + return false; + } } return true; } @@ -268,6 +295,11 @@ public class SystemSensorManager extends SensorManager { return false; } + public int flush() { + if (nSensorEventQueue == 0) throw new NullPointerException(); + return nativeFlushSensor(nSensorEventQueue); + } + public boolean hasSensors() { // no more sensors are set return mActiveSensors.indexOfValue(true) >= 0; @@ -295,11 +327,14 @@ public class SystemSensorManager extends SensorManager { } } - private int enableSensor(Sensor sensor, int us) { + private int enableSensor( + Sensor sensor, int rateUs, int maxBatchReportLatencyUs, int reservedFlags) { if (nSensorEventQueue == 0) throw new NullPointerException(); if (sensor == null) throw new NullPointerException(); - return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), us); + return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs, + maxBatchReportLatencyUs, reservedFlags); } + private int disableSensor(Sensor sensor) { if (nSensorEventQueue == 0) throw new NullPointerException(); if (sensor == null) throw new NullPointerException(); @@ -307,6 +342,7 @@ public class SystemSensorManager extends SensorManager { } protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy, long timestamp); + protected abstract void dispatchFlushCompleteEvent(int handle); protected abstract void addSensorEvent(Sensor sensor); protected abstract void removeSensorEvent(Sensor sensor); @@ -370,6 +406,15 @@ public class SystemSensorManager extends SensorManager { } mListener.onSensorChanged(t); } + + @SuppressWarnings("unused") + protected void dispatchFlushCompleteEvent(int handle) { + if (mListener instanceof SensorEventListener2) { + final Sensor sensor = sHandleToSensor.get(handle); + ((SensorEventListener2)mListener).onFlushCompleted(sensor); + } + return; + } } static final class TriggerEventQueue extends BaseEventQueue { @@ -415,5 +460,9 @@ public class SystemSensorManager extends SensorManager { mListener.onTrigger(t); } + + @SuppressWarnings("unused") + protected void dispatchFlushCompleteEvent(int handle) { + } } } diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java new file mode 100644 index 0000000000000000000000000000000000000000..1af575f12df5d15fd1ac62b46d4ae4acdb7beab8 --- /dev/null +++ b/core/java/android/hardware/camera2/CameraAccessException.java @@ -0,0 +1,137 @@ +/* + * 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.hardware.camera2; + +import android.util.AndroidException; + +/** + *

    CameraAccessException is thrown if a camera device could not + * be queried or opened by the {@link CameraManager}, or if the connection to an + * opened {@link CameraDevice} is no longer valid.

    + * + * @see CameraManager + * @see CameraDevice + */ +public class CameraAccessException extends AndroidException { + /** + * The camera device is in use already + * @hide + */ + public static final int CAMERA_IN_USE = 4; + + /** + * The system-wide limit for number of open cameras has been reached, + * and more camera devices cannot be opened until previous instances are + * closed. + * @hide + */ + public static final int MAX_CAMERAS_IN_USE = 5; + + /** + * The camera is disabled due to a device policy, and cannot be opened. + * + * @see android.app.admin.DevicePolicyManager#setCameraDisabled(android.content.ComponentName, boolean) + */ + public static final int CAMERA_DISABLED = 1; + + /** + * The camera device is removable and has been disconnected from the Android + * device, or the camera id used with {@link android.hardware.camera2.CameraManager#openCamera} + * is no longer valid, or the camera service has shut down the connection due to a + * higher-priority access request for the camera device. + */ + public static final int CAMERA_DISCONNECTED = 2; + + /** + * The camera device is currently in the error state. + * + *

    The camera has failed to open or has failed at a later time + * as a result of some non-user interaction. Refer to + * {@link CameraDevice.StateListener#onError} for the exact + * nature of the error.

    + * + *

    No further calls to the camera will succeed. Clean up + * the camera with {@link CameraDevice#close} and try + * handling the error in order to successfully re-open the camera. + *

    + * + */ + public static final int CAMERA_ERROR = 3; + + /** + * A deprecated HAL version is in use. + * @hide + */ + public static final int CAMERA_DEPRECATED_HAL = 1000; + + // Make the eclipse warning about serializable exceptions go away + private static final long serialVersionUID = 5630338637471475675L; // randomly generated + + private final int mReason; + + /** + * The reason for the failure to access the camera. + * + * @see #CAMERA_DISABLED + * @see #CAMERA_DISCONNECTED + * @see #CAMERA_ERROR + */ + public final int getReason() { + return mReason; + } + + public CameraAccessException(int problem) { + super(getDefaultMessage(problem)); + mReason = problem; + } + + public CameraAccessException(int problem, String message) { + super(message); + mReason = problem; + } + + public CameraAccessException(int problem, String message, Throwable cause) { + super(message, cause); + mReason = problem; + } + + public CameraAccessException(int problem, Throwable cause) { + super(getDefaultMessage(problem), cause); + mReason = problem; + } + + private static String getDefaultMessage(int problem) { + switch (problem) { + case CAMERA_IN_USE: + return "The camera device is in use already"; + case MAX_CAMERAS_IN_USE: + return "The system-wide limit for number of open cameras has been reached, " + + "and more camera devices cannot be opened until previous instances " + + "are closed."; + case CAMERA_DISCONNECTED: + return "The camera device is removable and has been disconnected from the " + + "Android device, or the camera service has shut down the connection due " + + "to a higher-priority access request for the camera device."; + case CAMERA_DISABLED: + return "The camera is disabled due to a device policy, and cannot be opened."; + case CAMERA_ERROR: + return "The camera device is currently in the error state; " + + "no further calls to it will succeed."; + } + return null; + } +} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java new file mode 100644 index 0000000000000000000000000000000000000000..4fe2c4d3669b5d68b1d8a06082f423732dca008b --- /dev/null +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -0,0 +1,604 @@ +/* + * 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.hardware.camera2; + +import android.hardware.camera2.impl.CameraMetadataNative; + +import java.util.Collections; +import java.util.List; + +/** + *

    The properties describing a + * {@link CameraDevice CameraDevice}.

    + * + *

    These properties are fixed for a given CameraDevice, and can be queried + * through the {@link CameraManager CameraManager} + * interface in addition to through the CameraDevice interface.

    + * + * @see CameraDevice + * @see CameraManager + */ +public final class CameraCharacteristics extends CameraMetadata { + + private final CameraMetadataNative mProperties; + private List> mAvailableRequestKeys; + private List> mAvailableResultKeys; + + /** + * Takes ownership of the passed-in properties object + * @hide + */ + public CameraCharacteristics(CameraMetadataNative properties) { + mProperties = properties; + } + + @Override + public T get(Key key) { + return mProperties.get(key); + } + + /** + * Returns the list of keys supported by this {@link CameraDevice} for querying + * with a {@link CaptureRequest}. + * + *

    The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.

    + * + *

    Each key is only listed once in the list. The order of the keys is undefined.

    + * + *

    Note that there is no {@code getAvailableCameraCharacteristicsKeys()} -- use + * {@link #getKeys()} instead.

    + * + * @return List of keys supported by this CameraDevice for CaptureRequests. + */ + public List> getAvailableCaptureRequestKeys() { + if (mAvailableRequestKeys == null) { + mAvailableRequestKeys = getAvailableKeyList(CaptureRequest.class); + } + return mAvailableRequestKeys; + } + + /** + * Returns the list of keys supported by this {@link CameraDevice} for querying + * with a {@link CaptureResult}. + * + *

    The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.

    + * + *

    Each key is only listed once in the list. The order of the keys is undefined.

    + * + *

    Note that there is no {@code getAvailableCameraCharacteristicsKeys()} -- use + * {@link #getKeys()} instead.

    + * + * @return List of keys supported by this CameraDevice for CaptureResults. + */ + public List> getAvailableCaptureResultKeys() { + if (mAvailableResultKeys == null) { + mAvailableResultKeys = getAvailableKeyList(CaptureResult.class); + } + return mAvailableResultKeys; + } + + /** + * Returns the list of keys supported by this {@link CameraDevice} by metadataClass. + * + *

    The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.

    + * + *

    Each key is only listed once in the list. The order of the keys is undefined.

    + * + * @param metadataClass The subclass of CameraMetadata that you want to get the keys for. + * + * @return List of keys supported by this CameraDevice for metadataClass. + * + * @throws IllegalArgumentException if metadataClass is not a subclass of CameraMetadata + */ + private List> getAvailableKeyList(Class metadataClass) { + + if (metadataClass.equals(CameraMetadata.class)) { + throw new AssertionError( + "metadataClass must be a strict subclass of CameraMetadata"); + } else if (!CameraMetadata.class.isAssignableFrom(metadataClass)) { + throw new AssertionError( + "metadataClass must be a subclass of CameraMetadata"); + } + + return Collections.unmodifiableList(getKeysStatic(metadataClass, /*instance*/null)); + } + + /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * The key entries below this point are generated from metadata + * definitions in /system/media/camera/docs. Do not modify by hand or + * modify the comment blocks at the start or end. + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ + + /** + *

    + * Which set of antibanding modes are + * supported + *

    + */ + public static final Key CONTROL_AE_AVAILABLE_ANTIBANDING_MODES = + new Key("android.control.aeAvailableAntibandingModes", byte[].class); + + /** + *

    + * List of frame rate ranges supported by the + * AE algorithm/hardware + *

    + */ + public static final Key CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES = + new Key("android.control.aeAvailableTargetFpsRanges", int[].class); + + /** + *

    + * Maximum and minimum exposure compensation + * setting, in counts of + * android.control.aeCompensationStepSize + *

    + */ + public static final Key CONTROL_AE_COMPENSATION_RANGE = + new Key("android.control.aeCompensationRange", int[].class); + + /** + *

    + * Smallest step by which exposure compensation + * can be changed + *

    + */ + public static final Key CONTROL_AE_COMPENSATION_STEP = + new Key("android.control.aeCompensationStep", Rational.class); + + /** + *

    + * List of AF modes that can be + * selected + *

    + */ + public static final Key CONTROL_AF_AVAILABLE_MODES = + new Key("android.control.afAvailableModes", byte[].class); + + /** + *

    + * what subset of the full color effect enum + * list is supported + *

    + */ + public static final Key CONTROL_AVAILABLE_EFFECTS = + new Key("android.control.availableEffects", byte[].class); + + /** + *

    + * what subset of the scene mode enum list is + * supported. + *

    + */ + public static final Key CONTROL_AVAILABLE_SCENE_MODES = + new Key("android.control.availableSceneModes", byte[].class); + + /** + *

    + * List of video stabilization modes that can + * be supported + *

    + */ + public static final Key CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES = + new Key("android.control.availableVideoStabilizationModes", byte[].class); + + /** + */ + public static final Key CONTROL_AWB_AVAILABLE_MODES = + new Key("android.control.awbAvailableModes", byte[].class); + + /** + *

    + * For AE, AWB, and AF, how many individual + * regions can be listed for metering? + *

    + */ + public static final Key CONTROL_MAX_REGIONS = + new Key("android.control.maxRegions", int.class); + + /** + *

    + * Whether this camera has a + * flash + *

    + *

    + * If no flash, none of the flash controls do + * anything. All other metadata should return 0 + *

    + */ + public static final Key FLASH_INFO_AVAILABLE = + new Key("android.flash.info.available", byte.class); + + /** + *

    + * Supported resolutions for the JPEG + * thumbnail + *

    + */ + public static final Key JPEG_AVAILABLE_THUMBNAIL_SIZES = + new Key("android.jpeg.availableThumbnailSizes", android.hardware.camera2.Size[].class); + + /** + *

    + * List of supported aperture + * values + *

    + *

    + * If variable aperture not available, only setting + * should be for the fixed aperture + *

    + */ + public static final Key LENS_INFO_AVAILABLE_APERTURES = + new Key("android.lens.info.availableApertures", float[].class); + + /** + *

    + * List of supported ND filter + * values + *

    + *

    + * If not available, only setting is 0. Otherwise, + * lists the available exposure index values for dimming + * (2 would mean the filter is set to reduce incoming + * light by two stops) + *

    + */ + public static final Key LENS_INFO_AVAILABLE_FILTER_DENSITIES = + new Key("android.lens.info.availableFilterDensities", float[].class); + + /** + *

    + * If fitted with optical zoom, what focal + * lengths are available. If not, the static focal + * length + *

    + *

    + * If optical zoom not supported, only one value + * should be reported + *

    + */ + public static final Key LENS_INFO_AVAILABLE_FOCAL_LENGTHS = + new Key("android.lens.info.availableFocalLengths", float[].class); + + /** + *

    + * List of supported optical image + * stabilization modes + *

    + */ + public static final Key LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION = + new Key("android.lens.info.availableOpticalStabilization", byte[].class); + + /** + *

    + * Hyperfocal distance for this lens; set to + * 0 if fixed focus + *

    + *

    + * The hyperfocal distance is used for the old + * API's 'fixed' setting + *

    + */ + public static final Key LENS_INFO_HYPERFOCAL_DISTANCE = + new Key("android.lens.info.hyperfocalDistance", float.class); + + /** + *

    + * Shortest distance from frontmost surface + * of the lens that can be focused correctly + *

    + *

    + * If the lens is fixed-focus, this should be + * 0 + *

    + */ + public static final Key LENS_INFO_MINIMUM_FOCUS_DISTANCE = + new Key("android.lens.info.minimumFocusDistance", float.class); + + /** + *

    + * Dimensions of lens shading + * map + *

    + */ + public static final Key LENS_INFO_SHADING_MAP_SIZE = + new Key("android.lens.info.shadingMapSize", android.hardware.camera2.Size.class); + + /** + *

    + * Direction the camera faces relative to + * device screen + *

    + * @see #LENS_FACING_FRONT + * @see #LENS_FACING_BACK + */ + public static final Key LENS_FACING = + new Key("android.lens.facing", int.class); + + /** + *

    + * How many output streams can be allocated at + * the same time for each type of stream + *

    + *

    + * Video snapshot with preview callbacks requires 3 + * processed streams (preview, record, app callbacks) and + * one JPEG stream (snapshot) + *

    + */ + public static final Key REQUEST_MAX_NUM_OUTPUT_STREAMS = + new Key("android.request.maxNumOutputStreams", int[].class); + + /** + *

    + * List of app-visible formats + *

    + */ + public static final Key SCALER_AVAILABLE_FORMATS = + new Key("android.scaler.availableFormats", int[].class); + + /** + *

    + * The minimum frame duration that is supported + * for each resolution in availableJpegSizes. Should + * correspond to the frame duration when only that JPEG + * stream is active and captured in a burst, with all + * processing set to FAST + *

    + *

    + * When multiple streams are configured, the minimum + * frame duration will be >= max(individual stream min + * durations) + *

    + */ + public static final Key SCALER_AVAILABLE_JPEG_MIN_DURATIONS = + new Key("android.scaler.availableJpegMinDurations", long[].class); + + /** + *

    + * The resolutions available for output from + * the JPEG block. Listed as width x height + *

    + */ + public static final Key SCALER_AVAILABLE_JPEG_SIZES = + new Key("android.scaler.availableJpegSizes", android.hardware.camera2.Size[].class); + + /** + *

    + * The maximum ratio between active area width + * and crop region width, or between active area height and + * crop region height, if the crop region height is larger + * than width + *

    + */ + public static final Key SCALER_AVAILABLE_MAX_DIGITAL_ZOOM = + new Key("android.scaler.availableMaxDigitalZoom", float.class); + + /** + *

    + * The minimum frame duration that is supported + * for each resolution in availableProcessedSizes. Should + * correspond to the frame duration when only that processed + * stream is active, with all processing set to + * FAST + *

    + *

    + * When multiple streams are configured, the minimum + * frame duration will be >= max(individual stream min + * durations) + *

    + */ + public static final Key SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS = + new Key("android.scaler.availableProcessedMinDurations", long[].class); + + /** + *

    + * The resolutions available for use with + * processed output streams, such as YV12, NV12, and + * platform opaque YUV/RGB streams to the GPU or video + * encoders. Listed as width, height + *

    + *

    + * The actual supported resolution list may be limited by + * consumer end points for different use cases. For example, for + * recording use case, the largest supported resolution may be + * limited by max supported size from encoder, for preview use + * case, the largest supported resolution may be limited by max + * resolution SurfaceTexture/SurfaceView can support. + *

    + */ + public static final Key SCALER_AVAILABLE_PROCESSED_SIZES = + new Key("android.scaler.availableProcessedSizes", android.hardware.camera2.Size[].class); + + /** + *

    + * Area of raw data which corresponds to only + * active pixels; smaller or equal to + * pixelArraySize. + *

    + */ + public static final Key SENSOR_INFO_ACTIVE_ARRAY_SIZE = + new Key("android.sensor.info.activeArraySize", android.graphics.Rect.class); + + /** + *

    + * Range of valid sensitivities + *

    + */ + public static final Key SENSOR_INFO_SENSITIVITY_RANGE = + new Key("android.sensor.info.sensitivityRange", int[].class); + + /** + *

    + * Range of valid exposure + * times + *

    + */ + public static final Key SENSOR_INFO_EXPOSURE_TIME_RANGE = + new Key("android.sensor.info.exposureTimeRange", long[].class); + + /** + *

    + * Maximum possible frame duration (minimum frame + * rate) + *

    + *

    + * Minimum duration is a function of resolution, + * processing settings. See + * android.scaler.availableProcessedMinDurations + * android.scaler.availableJpegMinDurations + * android.scaler.availableRawMinDurations + *

    + */ + public static final Key SENSOR_INFO_MAX_FRAME_DURATION = + new Key("android.sensor.info.maxFrameDuration", long.class); + + /** + *

    + * The physical dimensions of the full pixel + * array + *

    + *

    + * Needed for FOV calculation for old API + *

    + */ + public static final Key SENSOR_INFO_PHYSICAL_SIZE = + new Key("android.sensor.info.physicalSize", float[].class); + + /** + *

    + * Gain factor from electrons to raw units when + * ISO=100 + *

    + * + * Optional - This value may be null on some devices. + * + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} - + * Present on all devices that report being FULL level hardware devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key. + */ + public static final Key SENSOR_BASE_GAIN_FACTOR = + new Key("android.sensor.baseGainFactor", Rational.class); + + /** + *

    + * Maximum sensitivity that is implemented + * purely through analog gain + *

    + *

    + * For android.sensor.sensitivity values less than or + * equal to this, all applied gain must be analog. For + * values above this, it can be a mix of analog and + * digital + *

    + * + * Optional - This value may be null on some devices. + * + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} - + * Present on all devices that report being FULL level hardware devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key. + */ + public static final Key SENSOR_MAX_ANALOG_SENSITIVITY = + new Key("android.sensor.maxAnalogSensitivity", int.class); + + /** + *

    + * Clockwise angle through which the output + * image needs to be rotated to be upright on the device + * screen in its native orientation. Also defines the + * direction of rolling shutter readout, which is from top + * to bottom in the sensor's coordinate system + *

    + */ + public static final Key SENSOR_ORIENTATION = + new Key("android.sensor.orientation", int.class); + + /** + *

    + * Which face detection modes are available, + * if any + *

    + *

    + * OFF means face detection is disabled, it must + * be included in the list. + *

    + * SIMPLE means the device supports the + * android.statistics.faceRectangles and + * android.statistics.faceScores outputs. + *

    + * FULL means the device additionally supports the + * android.statistics.faceIds and + * android.statistics.faceLandmarks outputs. + *

    + */ + public static final Key STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES = + new Key("android.statistics.info.availableFaceDetectModes", byte[].class); + + /** + *

    + * Maximum number of simultaneously detectable + * faces + *

    + */ + public static final Key STATISTICS_INFO_MAX_FACE_COUNT = + new Key("android.statistics.info.maxFaceCount", int.class); + + /** + *

    + * Maximum number of supported points in the + * tonemap curve + *

    + */ + public static final Key TONEMAP_MAX_CURVE_POINTS = + new Key("android.tonemap.maxCurvePoints", int.class); + + /** + *

    + * A list of camera LEDs that are available on this system. + *

    + * @see #LED_AVAILABLE_LEDS_TRANSMIT + * + * @hide + */ + public static final Key LED_AVAILABLE_LEDS = + new Key("android.led.availableLeds", int[].class); + + /** + *

    + * The camera 3 HAL device can implement one of two possible + * operational modes; limited and full. Full support is + * expected from new higher-end devices. Limited mode has + * hardware requirements roughly in line with those for a + * camera HAL device v1 implementation, and is expected from + * older or inexpensive devices. Full is a strict superset of + * limited, and they share the same essential operational flow. + *

    + * For full details refer to "S3. Operational Modes" in camera3.h + *

    + * @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED + * @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL + */ + public static final Key INFO_SUPPORTED_HARDWARE_LEVEL = + new Key("android.info.supportedHardwareLevel", int.class); + + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * End generated code + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ +} diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java new file mode 100644 index 0000000000000000000000000000000000000000..7095e4d498ab6d7f8aacfb48a0e8674b0a05d283 --- /dev/null +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -0,0 +1,992 @@ +/* + * 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.hardware.camera2; + +import android.os.Handler; +import android.view.Surface; + +import java.util.List; + +/** + *

    The CameraDevice class is an interface to a single camera connected to an + * Android device, allowing for fine-grain control of image capture and + * post-processing at high frame rates.

    + * + *

    Your application must declare the + * {@link android.Manifest.permission#CAMERA Camera} permission in its manifest + * in order to access camera devices.

    + * + *

    A given camera device may provide support at one of two levels: limited or + * full. If a device only supports the limited level, then Camera2 exposes a + * feature set that is roughly equivalent to the older + * {@link android.hardware.Camera Camera} API, although with a cleaner and more + * efficient interface. Devices that implement the full level of support + * provide substantially improved capabilities over the older camera + * API. Applications that target the limited level devices will run unchanged on + * the full-level devices; if your application requires a full-level device for + * proper operation, declare the "android.hardware.camera2.full" feature in your + * manifest.

    + * + * @see CameraManager#openCamera + * @see android.Manifest.permission#CAMERA + */ +public interface CameraDevice extends AutoCloseable { + + /** + * Create a request suitable for a camera preview window. Specifically, this + * means that high frame rate is given priority over the highest-quality + * post-processing. These requests would normally be used with the + * {@link #setRepeatingRequest} method. + * + * @see #createCaptureRequest + */ + public static final int TEMPLATE_PREVIEW = 1; + + /** + * Create a request suitable for still image capture. Specifically, this + * means prioritizing image quality over frame rate. These requests would + * commonly be used with the {@link #capture} method. + * + * @see #createCaptureRequest + */ + public static final int TEMPLATE_STILL_CAPTURE = 2; + + /** + * Create a request suitable for video recording. Specifically, this means + * that a stable frame rate is used, and post-processing is set for + * recording quality. These requests would commonly be used with the + * {@link #setRepeatingRequest} method. + * + * @see #createCaptureRequest + */ + public static final int TEMPLATE_RECORD = 3; + + /** + * Create a request suitable for still image capture while recording + * video. Specifically, this means maximizing image quality without + * disrupting the ongoing recording. These requests would commonly be used + * with the {@link #capture} method while a request based on + * {@link #TEMPLATE_RECORD} is is in use with {@link #setRepeatingRequest}. + * + * @see #createCaptureRequest + */ + public static final int TEMPLATE_VIDEO_SNAPSHOT = 4; + + /** + * Create a request suitable for zero shutter lag still capture. This means + * means maximizing image quality without compromising preview frame rate. + * AE/AWB/AF should be on auto mode. + * + * @see #createCaptureRequest + * @hide + */ + public static final int TEMPLATE_ZERO_SHUTTER_LAG = 5; + + /** + * A basic template for direct application control of capture + * parameters. All automatic control is disabled (auto-exposure, auto-white + * balance, auto-focus), and post-processing parameters are set to preview + * quality. The manual capture parameters (exposure, sensitivity, and so on) + * are set to reasonable defaults, but should be overriden by the + * application depending on the intended use case. + * + * @see #createCaptureRequest + * @hide + */ + public static final int TEMPLATE_MANUAL = 6; + + /** + * Get the ID of this camera device. + * + *

    This matches the ID given to {@link CameraManager#openCamera} to instantiate this + * this camera device.

    + * + *

    This ID can be used to query the camera device's {@link + * CameraCharacteristics fixed properties} with {@link + * CameraManager#getCameraCharacteristics}.

    + * + *

    This method can be called even if the device has been closed or has encountered + * a serious error.

    + * + * @return the ID for this camera device + * + * @see CameraManager#getCameraCharacteristics + * @see CameraManager#getCameraIdList + */ + public String getId(); + + /** + *

    Set up a new output set of Surfaces for the camera device.

    + * + *

    The configuration determines the set of potential output Surfaces for + * the camera device for each capture request. A given request may use all + * or a only some of the outputs. This method must be called before requests + * can be submitted to the camera with {@link #capture capture}, + * {@link #captureBurst captureBurst}, + * {@link #setRepeatingRequest setRepeatingRequest}, or + * {@link #setRepeatingBurst setRepeatingBurst}.

    + * + *

    Surfaces suitable for inclusion as a camera output can be created for + * various use cases and targets:

    + * + *
      + * + *
    • For drawing to a {@link android.view.SurfaceView SurfaceView}: Set + * the size of the Surface with + * {@link android.view.SurfaceHolder#setFixedSize} to be one of the + * supported + * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes} + * before calling {@link android.view.SurfaceHolder#getSurface}.
    • + * + *
    • For accessing through an OpenGL texture via a + * {@link android.graphics.SurfaceTexture SurfaceTexture}: Set the size of + * the SurfaceTexture with + * {@link android.graphics.SurfaceTexture#setDefaultBufferSize} to be one + * of the supported + * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes} + * before creating a Surface from the SurfaceTexture with + * {@link Surface#Surface}.
    • + * + *
    • For recording with {@link android.media.MediaCodec}: Call + * {@link android.media.MediaCodec#createInputSurface} after configuring + * the media codec to use one of the + * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes} + *
    • + * + *
    • For recording with {@link android.media.MediaRecorder}: TODO
    • + * + *
    • For efficient YUV processing with {@link android.renderscript}: + * Create a RenderScript + * {@link android.renderscript.Allocation Allocation} with a supported YUV + * type, the IO_INPUT flag, and one of the supported + * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes}. Then + * obtain the Surface with + * {@link android.renderscript.Allocation#getSurface}.
    • + * + *
    • For access to uncompressed or JPEG data in the application: Create a + * {@link android.media.ImageReader} object with the desired + * {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS image format}, and a + * size from the matching + * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed}, + * {@link CameraCharacteristics#SCALER_AVAILABLE_JPEG_SIZES jpeg}. Then obtain + * a Surface from it.
    • + * + *
    + * + *

    + * + *

    This function can take several hundred milliseconds to execute, since + * camera hardware may need to be powered on or reconfigured.

    + * + *

    The camera device will query each Surface's size and formats upon this + * call, so they must be set to a valid setting at this time (in particular: + * if the format is user-visible, it must be one of android.scaler.availableFormats; + * and the size must be one of android.scaler.available[Processed|Jpeg]Sizes).

    + * + *

    When this method is called with valid Surfaces, the device will transition to the {@link + * StateListener#onBusy busy state}. Once configuration is complete, the device will transition + * into the {@link StateListener#onIdle idle state}. Capture requests using the newly-configured + * Surfaces may then be submitted with {@link #capture}, {@link #captureBurst}, {@link + * #setRepeatingRequest}, or {@link #setRepeatingBurst}.

    + * + *

    If this method is called while the camera device is still actively processing previously + * submitted captures, then the following sequence of events occurs: The device transitions to + * the busy state and calls the {@link StateListener#onBusy} callback. Second, if a repeating + * request is set it is cleared. Third, the device finishes up all in-flight and pending + * requests. Finally, once the device is idle, it then reconfigures its outputs, and calls the + * {@link StateListener#onIdle} method once it is again ready to accept capture + * requests. Therefore, no submitted work is discarded. To idle as fast as possible, use {@link + * #flush} and wait for the idle callback before calling configureOutputs. This will discard + * work, but reaches the new configuration sooner.

    + * + *

    Using larger resolution outputs, or more outputs, can result in slower + * output rate from the device.

    + * + *

    Configuring the outputs with an empty or null list will transition the camera into an + * {@link StateListener#onUnconfigured unconfigured state} instead of the {@link + * StateListener#onIdle idle state}.

    + * + *

    Calling configureOutputs with the same arguments as the last call to + * configureOutputs has no effect, and the {@link StateListener#onBusy busy} + * and {@link StateListener#onIdle idle} state transitions will happen + * immediately.

    + * + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * + * @throws IllegalArgumentException if the set of output Surfaces do not + * meet the requirements + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device is not idle, or + * if the camera device has been closed + * + * @see StateListener#onBusy + * @see StateListener#onIdle + * @see StateListener#onActive + * @see StateListener#onUnconfigured + * @see #stopRepeating + * @see #flush + */ + public void configureOutputs(List outputs) throws CameraAccessException; + + /** + *

    Create a {@link CaptureRequest.Builder} for new capture requests, + * initialized with template for a target use case. The settings are chosen + * to be the best options for the specific camera device, so it is not + * recommended to reuse the same request for a different camera device; + * create a builder specific for that device and template and override the + * settings as desired, instead.

    + * + * @param templateType An enumeration selecting the use case for this + * request; one of the CameraDevice.TEMPLATE_ values. + * @return a builder for a capture request, initialized with default + * settings for that template, and no output streams + * + * @throws IllegalArgumentException if the templateType is not in the list + * of supported templates. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #TEMPLATE_PREVIEW + * @see #TEMPLATE_RECORD + * @see #TEMPLATE_STILL_CAPTURE + * @see #TEMPLATE_VIDEO_SNAPSHOT + * @see #TEMPLATE_MANUAL + */ + public CaptureRequest.Builder createCaptureRequest(int templateType) + throws CameraAccessException; + + /** + *

    Submit a request for an image to be captured by this CameraDevice.

    + * + *

    The request defines all the parameters for capturing the single image, + * including sensor, lens, flash, and post-processing settings.

    + * + *

    Each request will produce one {@link CaptureResult} and produce new + * frames for one or more target Surfaces, set with the CaptureRequest + * builder's {@link CaptureRequest.Builder#addTarget} method. The target + * surfaces must be configured as active outputs with + * {@link #configureOutputs} before calling this method.

    + * + *

    Multiple requests can be in progress at once. They are processed in + * first-in, first-out order, with minimal delays between each + * capture. Requests submitted through this method have higher priority than + * those submitted through {@link #setRepeatingRequest} or + * {@link #setRepeatingBurst}, and will be processed as soon as the current + * repeat/repeatBurst processing completes.

    + * + * @param request the settings for this capture + * @param listener The callback object to notify once this request has been + * processed. If null, no metadata will be produced for this capture, + * although image data will still be produced. + * @param handler the handler on which the listener should be invoked, or + * {@code null} to use the current thread's {@link android.os.Looper + * looper}. + * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera is currently busy or unconfigured, + * or the camera device has been closed. + * @throws IllegalArgumentException If the request targets Surfaces not + * currently configured as outputs. Or if the handler is null, the listener + * is not null, and the calling thread has no looper. + * + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public int capture(CaptureRequest request, CaptureListener listener, Handler handler) + throws CameraAccessException; + + /** + * Submit a list of requests to be captured in sequence as a burst. The + * burst will be captured in the minimum amount of time possible, and will + * not be interleaved with requests submitted by other capture or repeat + * calls. + * + *

    The requests will be captured in order, each capture producing one + * {@link CaptureResult} and image buffers for one or more target + * {@link android.view.Surface surfaces}. The target surfaces for each + * request (set with {@link CaptureRequest.Builder#addTarget}) must be + * configured as active outputs with {@link #configureOutputs} before + * calling this method.

    + * + *

    The main difference between this method and simply calling + * {@link #capture} repeatedly is that this method guarantees that no + * other requests will be interspersed with the burst.

    + * + * @param requests the list of settings for this burst capture + * @param listener The callback object to notify each time one of the + * requests in the burst has been processed. If null, no metadata will be + * produced for any requests in this burst, although image data will still + * be produced. + * @param handler the handler on which the listener should be invoked, or + * {@code null} to use the current thread's {@link android.os.Looper + * looper}. + * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera is currently busy or unconfigured, + * or the camera device has been closed. + * @throws IllegalArgumentException If the requests target Surfaces not + * currently configured as outputs. Or if the handler is null, the listener + * is not null, and the calling thread has no looper. + * + * @see #capture + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public int captureBurst(List requests, CaptureListener listener, + Handler handler) throws CameraAccessException; + + /** + * Request endlessly repeating capture of images by this CameraDevice. + * + *

    With this method, the CameraDevice will continually capture images + * using the settings in the provided {@link CaptureRequest}, at the maximum + * rate possible.

    + * + *

    Repeating requests are a simple way for an application to maintain a + * preview or other continuous stream of frames, without having to + * continually submit identical requests through {@link #capture}.

    + * + *

    Repeat requests have lower priority than those submitted + * through {@link #capture} or {@link #captureBurst}, so if + * {@link #capture} is called when a repeating request is active, the + * capture request will be processed before any further repeating + * requests are processed.

    + * + *

    Repeating requests are a simple way for an application to maintain a + * preview or other continuous stream of frames, without having to submit + * requests through {@link #capture} at video rates.

    + * + *

    To stop the repeating capture, call {@link #stopRepeating}. Calling + * {@link #flush} will also clear the request.

    + * + *

    Calling this method will replace any earlier repeating request or + * burst set up by this method or {@link #setRepeatingBurst}, although any + * in-progress burst will be completed before the new repeat request will be + * used.

    + * + * @param request the request to repeat indefinitely + * @param listener The callback object to notify every time the + * request finishes processing. If null, no metadata will be + * produced for this stream of requests, although image data will + * still be produced. + * @param handler the handler on which the listener should be invoked, or + * {@code null} to use the current thread's {@link android.os.Looper + * looper}. + * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera is currently busy or unconfigured, + * or the camera device has been closed. + * @throws IllegalArgumentException If the requests reference Surfaces not + * currently configured as outputs. Or if the handler is null, the listener + * is not null, and the calling thread has no looper. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingBurst + * @see #stopRepeating + * @see #flush + */ + public int setRepeatingRequest(CaptureRequest request, CaptureListener listener, + Handler handler) throws CameraAccessException; + + /** + *

    Request endlessly repeating capture of a sequence of images by this + * CameraDevice.

    + * + *

    With this method, the CameraDevice will continually capture images, + * cycling through the settings in the provided list of + * {@link CaptureRequest CaptureRequests}, at the maximum rate possible.

    + * + *

    If a request is submitted through {@link #capture} or + * {@link #captureBurst}, the current repetition of the request list will be + * completed before the higher-priority request is handled. This guarantees + * that the application always receives a complete repeat burst captured in + * minimal time, instead of bursts interleaved with higher-priority + * captures, or incomplete captures.

    + * + *

    Repeating burst requests are a simple way for an application to + * maintain a preview or other continuous stream of frames where each + * request is different in a predicatable way, without having to continually + * submit requests through {@link #captureBurst} .

    + * + *

    To stop the repeating capture, call {@link #stopRepeating}. Any + * ongoing burst will still be completed, however. Calling + * {@link #flush} will also clear the request.

    + * + *

    Calling this method will replace a previously-set repeating request or + * burst set up by this method or {@link #setRepeatingRequest}, although any + * in-progress burst will be completed before the new repeat burst will be + * used.

    + * + * @param requests the list of requests to cycle through indefinitely + * @param listener The callback object to notify each time one of the + * requests in the repeating bursts has finished processing. If null, no + * metadata will be produced for this stream of requests, although image + * data will still be produced. + * @param handler the handler on which the listener should be invoked, or + * {@code null} to use the current thread's {@link android.os.Looper + * looper}. + * + * @return int A unique capture sequence ID used by + * {@link CaptureListener#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera is currently busy or unconfigured, + * or the camera device has been closed. + * @throws IllegalArgumentException If the requests reference Surfaces not + * currently configured as outputs. Or if the handler is null, the listener + * is not null, and the calling thread has no looper. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #stopRepeating + * @see #flush + */ + public int setRepeatingBurst(List requests, CaptureListener listener, + Handler handler) throws CameraAccessException; + + /** + *

    Cancel any ongoing repeating capture set by either + * {@link #setRepeatingRequest setRepeatingRequest} or + * {@link #setRepeatingBurst}. Has no effect on requests submitted through + * {@link #capture capture} or {@link #captureBurst captureBurst}.

    + * + *

    Any currently in-flight captures will still complete, as will any + * burst that is mid-capture. To ensure that the device has finished + * processing all of its capture requests and is in idle state, wait for the + * {@link StateListener#onIdle} callback after calling this + * method..

    + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera is currently busy or unconfigured, + * or the camera device has been closed. + * + * @see #setRepeatingRequest + * @see #setRepeatingBurst + * @see StateListener#onIdle + */ + public void stopRepeating() throws CameraAccessException; + + /** + *

    Wait until all the submitted requests have finished processing

    + * + *

    This method blocks until all the requests that have been submitted to + * the camera device, either through {@link #capture capture}, + * {@link #captureBurst captureBurst}, + * {@link #setRepeatingRequest setRepeatingRequest}, or + * {@link #setRepeatingBurst setRepeatingBurst}, have completed their + * processing.

    + * + *

    Once this call returns successfully, the device is in an idle state, + * and can be reconfigured with {@link #configureOutputs configureOutputs}.

    + * + *

    This method cannot be used if there is an active repeating request or + * burst, set with {@link #setRepeatingRequest setRepeatingRequest} or + * {@link #setRepeatingBurst setRepeatingBurst}. Call + * {@link #stopRepeating stopRepeating} before calling this method.

    + * + * @throws CameraAccessException if the camera device is no longer connected + * @throws IllegalStateException if the camera device has been closed, the + * device has encountered a fatal error, or if there is an active repeating + * request or burst. + */ + public void waitUntilIdle() throws CameraAccessException; + + /** + * Flush all captures currently pending and in-progress as fast as + * possible. + * + *

    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#onCaptureCompleted}, 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.

    + * + *

    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.

    + * + *

    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.

    + * + *

    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).

    + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera is not idle/active, + * or the camera device has been closed. + * + * @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(). Once the device has fully shut down, the + * {@link StateListener#onClosed} callback will be called, and the camera is + * free to be re-opened.

    + * + *

    After this call, besides the final {@link StateListener#onClosed} call, no calls to the + * device's {@link StateListener} will occur, and any remaining submitted capture requests will + * not fire their {@link CaptureListener} callbacks.

    + * + *

    To shut down as fast as possible, call the {@link #flush} method and then {@link #close} + * once the flush completes. This will discard some capture requests, but results in faster + * shutdown.

    + */ + @Override + public void close(); + + /** + *

    A listener for tracking the progress of a {@link CaptureRequest} + * submitted to the camera device.

    + * + *

    This listener is called when a request triggers a capture to start, + * and when the capture is complete. In case on an error capturing an image, + * the error method is triggered instead of the completion method.

    + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public static abstract class CaptureListener { + + /** + * This method is called when the camera device has started capturing + * the output image for the request, at the beginning of image exposure. + * + *

    This callback is invoked right as the capture of a frame begins, + * so it is the most appropriate time for playing a shutter sound, + * or triggering UI indicators of capture.

    + * + *

    The request that is being used for this capture is provided, along + * with the actual timestamp for the start of exposure. This timestamp + * matches the timestamp that will be included in + * {@link CaptureResult#SENSOR_TIMESTAMP the result timestamp field}, + * and in the buffers sent to each output Surface. These buffer + * timestamps are accessible through, for example, + * {@link android.media.Image#getTimestamp() Image.getTimestamp()} or + * {@link android.graphics.SurfaceTexture#getTimestamp()}.

    + * + *

    For the simplest way to play a shutter sound camera shutter or a + * video recording start/stop sound, see the + * {@link android.media.MediaActionSound} class.

    + * + *

    The default implementation of this method does nothing.

    + * + * @param camera the CameraDevice sending the callback + * @param request the request for the capture that just begun + * @param timestamp the timestamp at start of capture, in nanoseconds. + * + * @see android.media.MediaActionSound + */ + public void onCaptureStarted(CameraDevice camera, + CaptureRequest request, long timestamp) { + // default empty implementation + } + + /** + * This method is called when an image capture has completed and the + * result metadata is available. + * + *

    The default implementation of this method does nothing.

    + * + * @param camera The CameraDevice sending the callback. + * @param request The request that was given to the CameraDevice + * @param result The output metadata from the capture, including the + * final capture parameters and the state of the camera system during + * capture. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public void onCaptureCompleted(CameraDevice camera, + CaptureRequest request, CaptureResult result) { + // default empty implementation + } + + /** + * This method is called instead of {@link #onCaptureCompleted} when the + * camera device failed to produce a {@link CaptureResult} for the + * request. + * + *

    Other requests are unaffected, and some or all image buffers from + * the capture may have been pushed to their respective output + * streams.

    + * + *

    The default implementation of this method does nothing.

    + * + * @param camera + * The CameraDevice sending the callback. + * @param request + * The request that was given to the CameraDevice + * @param failure + * The output failure from the capture, including the failure reason + * and the frame number. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public void onCaptureFailed(CameraDevice camera, + CaptureRequest request, CaptureFailure failure) { + // default empty implementation + } + + /** + * This method is called independently of the others in CaptureListener, + * when a capture sequence finishes and all {@link CaptureResult} + * or {@link CaptureFailure} for it have been returned via this listener. + * + * @param camera + * The CameraDevice sending the callback. + * @param sequenceId + * A sequence ID returned by the {@link #capture} family of functions. + * @param frameNumber + * The last frame number (returned by {@link CaptureResult#getFrameNumber} + * or {@link CaptureFailure#getFrameNumber}) in the capture sequence. + * + * @see CaptureResult#getFrameNumber() + * @see CaptureFailure#getFrameNumber() + * @see CaptureResult#getSequenceId() + * @see CaptureFailure#getSequenceId() + */ + public void onCaptureSequenceCompleted(CameraDevice camera, + int sequenceId, int frameNumber) { + // default empty implementation + } + } + + /** + * A listener for notifications about the state of a camera + * device. + * + *

    A listener must be provided to the {@link CameraManager#openCamera} + * method to open a camera device.

    + * + *

    These events include notifications about the device becoming idle ( + * allowing for {@link #configureOutputs} to be called), about device + * disconnection, and about unexpected device errors.

    + * + *

    Events about the progress of specific {@link CaptureRequest + * CaptureRequests} are provided through a {@link CaptureListener} given to + * the {@link #capture}, {@link #captureBurst}, {@link + * #setRepeatingRequest}, or {@link #setRepeatingBurst} methods. + * + * @see CameraManager#openCamera + */ + public static abstract class StateListener { + /** + * An error code that can be reported by {@link #onError} + * indicating that the camera device is in use already. + * + *

    + * This error can be produced when opening the camera fails. + *

    + * + * @see #onError + */ + public static final int ERROR_CAMERA_IN_USE = 1; + + /** + * An error code that can be reported by {@link #onError} + * indicating that the camera device could not be opened + * because there are too many other open camera devices. + * + *

    + * The system-wide limit for number of open cameras has been reached, + * and more camera devices cannot be opened until previous instances are + * closed. + *

    + * + *

    + * This error can be produced when opening the camera fails. + *

    + * + * @see #onError + */ + public static final int ERROR_MAX_CAMERAS_IN_USE = 2; + + /** + * An error code that can be reported by {@link #onError} + * indicating that the camera device could not be opened due to a device + * policy. + * + * @see android.app.admin.DevicePolicyManager#setCameraDisabled(android.content.ComponentName, boolean) + * @see #onError + */ + public static final int ERROR_CAMERA_DISABLED = 3; + + /** + * An error code that can be reported by {@link #onError} + * indicating that the camera device has encountered a fatal error. + * + *

    The camera device needs to be re-opened to be used again.

    + * + * @see #onError + */ + public static final int ERROR_CAMERA_DEVICE = 4; + + /** + * An error code that can be reported by {@link #onError} + * indicating that the camera service has encountered a fatal error. + * + *

    The Android device may need to be shut down and restarted to restore + * camera function, or there may be a persistent hardware problem.

    + * + *

    An attempt at recovery may be possible by closing the + * CameraDevice and the CameraManager, and trying to acquire all resources + * again from scratch.

    + * + * @see #onError + */ + public static final int ERROR_CAMERA_SERVICE = 5; + + /** + * The method called when a camera device has finished opening. + * + *

    An opened camera will immediately afterwards transition into + * {@link #onUnconfigured}.

    + * + * @param camera the camera device that has become opened + */ + public abstract void onOpened(CameraDevice camera); // Must implement + + /** + * The method called when a camera device has no outputs configured. + * + *

    An unconfigured camera device needs to be configured with + * {@link CameraDevice#configureOutputs} before being able to + * submit any capture request.

    + * + *

    This state may be entered by a newly opened camera or by + * calling {@link CameraDevice#configureOutputs} with a null/empty + * list of Surfaces when idle.

    + * + *

    Any attempts to submit a capture request while in this state + * will result in an {@link IllegalStateException} being thrown.

    + * + *

    The default implementation of this method does nothing.

    + * + * @param camera the camera device has that become unconfigured + */ + public void onUnconfigured(CameraDevice camera) { + // Default empty implementation + } + + /** + * The method called when a camera device begins processing + * {@link CaptureRequest capture requests}. + * + *

    A camera may not be re-configured while in this state. The camera + * will transition to the idle state once all pending captures have + * completed. If a repeating request is set, the camera will remain active + * until it is cleared and the remaining requests finish processing. To + * transition to the idle state as quickly as possible, call {@link #flush()}, + * which will idle the camera device as quickly as possible, likely canceling + * most in-progress captures.

    + * + *

    All calls except for {@link CameraDevice#configureOutputs} are + * legal while in this state. + *

    + * + *

    The default implementation of this method does nothing.

    + * + * @param camera the camera device that has become active + * + * @see CameraDevice#capture + * @see CameraDevice#captureBurst + * @see CameraDevice#setRepeatingBurst + * @see CameraDevice#setRepeatingRequest + */ + public void onActive(CameraDevice camera) { + // Default empty implementation + } + + /** + * The method called when a camera device is busy. + * + *

    A camera becomes busy while it's outputs are being configured + * (after a call to {@link CameraDevice#configureOutputs} or while it's + * being flushed (after a call to {@link CameraDevice#flush}.

    + * + *

    Once the on-going operations are complete, the camera will automatically + * transition into {@link #onIdle} if there is at least one configured output, + * or {@link #onUnconfigured} otherwise.

    + * + *

    Any attempts to manipulate the camera while its is busy + * will result in an {@link IllegalStateException} being thrown.

    + * + *

    Only the following methods are valid to call while in this state: + *

      + *
    • {@link CameraDevice#getId}
    • + *
    • {@link CameraDevice#createCaptureRequest}
    • + *
    • {@link CameraDevice#close}
    • + *
    + *

    + * + *

    The default implementation of this method does nothing.

    + * + * @param camera the camera device that has become busy + * + * @see CameraDevice#configureOutputs + * @see CameraDevice#flush + */ + public void onBusy(CameraDevice camera) { + // Default empty implementation + } + + /** + * The method called when a camera device has been closed with + * {@link CameraDevice#close}. + * + *

    Any attempt to call methods on this CameraDevice in the + * future will throw a {@link IllegalStateException}.

    + * + *

    The default implementation of this method does nothing.

    + * + * @param camera the camera device that has become closed + */ + public void onClosed(CameraDevice camera) { + // Default empty implementation + } + + /** + * The method called when a camera device has finished processing all + * submitted capture requests and has reached an idle state. + * + *

    An idle camera device can have its outputs changed by calling {@link + * CameraDevice#configureOutputs}, which will transition it into the busy state.

    + * + *

    To idle and reconfigure outputs without canceling any submitted + * capture requests, the application needs to clear its repeating + * request/burst, if set, with {@link CameraDevice#stopRepeating}, and + * then wait for this callback to be called before calling {@link + * CameraDevice#configureOutputs}.

    + * + *

    To idle and reconfigure a camera device as fast as possible, the + * {@link CameraDevice#flush} method can be used, which will discard all + * pending and in-progress capture requests. Once the {@link + * CameraDevice#flush} method is called, the application must wait for + * this callback to fire before calling {@link + * CameraDevice#configureOutputs}.

    + * + *

    The default implementation of this method does nothing.

    + * + * @param camera the camera device that has become idle + * + * @see CameraDevice#configureOutputs + * @see CameraDevice#stopRepeating + * @see CameraDevice#flush + */ + public void onIdle(CameraDevice camera) { + // Default empty implementation + } + + /** + * The method called when a camera device is no longer available for + * use. + * + *

    This callback may be called instead of {@link #onOpened} + * if opening the camera fails.

    + * + *

    Any attempt to call methods on this CameraDevice will throw a + * {@link CameraAccessException}. The disconnection could be due to a + * change in security policy or permissions; the physical disconnection + * of a removable camera device; or the camera being needed for a + * higher-priority use case.

    + * + *

    There may still be capture listener callbacks that are called + * after this method is called, or new image buffers that are delivered + * to active outputs.

    + * + *

    The default implementation logs a notice to the system log + * about the disconnection.

    + * + *

    You should clean up the camera with {@link CameraDevice#close} after + * this happens, as it is not recoverable until opening the camera again + * after it becomes {@link CameraManager.AvailabilityListener#onCameraAvailable available}. + *

    + * + * @param camera the device that has been disconnected + */ + public abstract void onDisconnected(CameraDevice camera); // Must implement + + /** + * The method called when a camera device has encountered a serious error. + * + *

    This callback may be called instead of {@link #onOpened} + * if opening the camera fails.

    + * + *

    This indicates a failure of the camera device or camera service in + * some way. Any attempt to call methods on this CameraDevice in the + * future will throw a {@link CameraAccessException} with the + * {@link CameraAccessException#CAMERA_ERROR CAMERA_ERROR} reason. + *

    + * + *

    There may still be capture completion or camera stream listeners + * that will be called after this error is received.

    + * + *

    You should clean up the camera with {@link CameraDevice#close} after + * this happens. Further attempts at recovery are error-code specific.

    + * + * @param camera The device reporting the error + * @param error The error code, one of the + * {@code CameraDeviceListener.ERROR_*} values. + * + * @see #ERROR_CAMERA_DEVICE + * @see #ERROR_CAMERA_SERVICE + * @see #ERROR_CAMERA_DISABLED + * @see #ERROR_CAMERA_IN_USE + */ + public abstract void onError(CameraDevice camera, int error); // Must implement + } +} diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java new file mode 100644 index 0000000000000000000000000000000000000000..65b6c7a09443151d04819e627604ee42f38bfed2 --- /dev/null +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -0,0 +1,520 @@ +/* + * 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.hardware.camera2; + +import android.content.Context; +import android.hardware.ICameraService; +import android.hardware.ICameraServiceListener; +import android.hardware.IProCameraUser; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.CameraBinderDecorator; +import android.hardware.camera2.utils.CameraRuntimeException; +import android.hardware.camera2.utils.BinderHolder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.ArrayMap; + +import java.util.ArrayList; + +/** + *

    An interface for iterating, listing, and connecting to + * {@link CameraDevice CameraDevices}.

    + * + *

    You can get an instance of this class by calling + * {@link android.content.Context#getSystemService(String) Context.getSystemService()}.

    + * + *
    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    + * + *

    For more details about communicating with camera devices, read the Camera + * developer guide or the {@link android.hardware.camera2 camera2} + * package documentation.

    + */ +public final class CameraManager { + + /** + * This should match the ICameraService definition + */ + private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; + private static final int USE_CALLING_UID = -1; + + private final ICameraService mCameraService; + private ArrayList mDeviceIdList; + + private final ArrayMap mListenerMap = + new ArrayMap(); + + private final Context mContext; + private final Object mLock = new Object(); + + /** + * @hide + */ + public CameraManager(Context context) { + mContext = context; + + IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); + ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); + + /** + * Wrap the camera service in a decorator which automatically translates return codes + * into exceptions, and RemoteExceptions into other exceptions. + */ + mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw); + + try { + mCameraService.addListener(new CameraServiceListener()); + } catch(CameraRuntimeException e) { + throw new IllegalStateException("Failed to register a camera service listener", + e.asChecked()); + } catch (RemoteException e) { + // impossible + } + } + + /** + * Return the list of currently connected camera devices by + * identifier. + * + *

    Non-removable cameras use integers starting at 0 for their + * identifiers, while removable cameras have a unique identifier for each + * individual device, even if they are the same model.

    + * + * @return The list of currently connected camera devices. + */ + public String[] getCameraIdList() throws CameraAccessException { + synchronized (mLock) { + try { + return getOrCreateDeviceIdListLocked().toArray(new String[0]); + } catch(CameraAccessException e) { + // this should almost never happen, except if mediaserver crashes + throw new IllegalStateException( + "Failed to query camera service for device ID list", e); + } + } + } + + /** + * Register a listener to be notified about camera device availability. + * + *

    Registering the same listener again will replace the handler with the + * new one provided.

    + * + * @param listener The new listener to send camera availability notices to + * @param handler The handler on which the listener should be invoked, or + * {@code null} to use the current thread's {@link android.os.Looper looper}. + */ + public void addAvailabilityListener(AvailabilityListener listener, Handler handler) { + if (handler == null) { + Looper looper = Looper.myLooper(); + if (looper == null) { + throw new IllegalArgumentException( + "No handler given, and current thread has no looper!"); + } + handler = new Handler(looper); + } + + synchronized (mLock) { + mListenerMap.put(listener, handler); + } + } + + /** + * Remove a previously-added listener; the listener will no longer receive + * connection and disconnection callbacks. + * + *

    Removing a listener that isn't registered has no effect.

    + * + * @param listener The listener to remove from the notification list + */ + public void removeAvailabilityListener(AvailabilityListener listener) { + synchronized (mLock) { + mListenerMap.remove(listener); + } + } + + /** + *

    Query the capabilities of a camera device. These capabilities are + * immutable for a given camera.

    + * + * @param cameraId The id of the camera device to query + * @return The properties of the given camera + * + * @throws IllegalArgumentException if the cameraId does not match any + * currently connected camera device. + * @throws CameraAccessException if the camera is disabled by device policy. + * @throws SecurityException if the application does not have permission to + * access the camera + * + * @see #getCameraIdList + * @see android.app.admin.DevicePolicyManager#setCameraDisabled + */ + public CameraCharacteristics getCameraCharacteristics(String cameraId) + throws CameraAccessException { + + synchronized (mLock) { + if (!getOrCreateDeviceIdListLocked().contains(cameraId)) { + throw new IllegalArgumentException(String.format("Camera id %s does not match any" + + " currently connected camera device", cameraId)); + } + } + + CameraMetadataNative info = new CameraMetadataNative(); + try { + mCameraService.getCameraCharacteristics(Integer.valueOf(cameraId), info); + } catch(CameraRuntimeException e) { + throw e.asChecked(); + } catch(RemoteException e) { + // impossible + return null; + } + + return new CameraCharacteristics(info); + } + + /** + * Open a connection to a camera with the given ID. Use + * {@link #getCameraIdList} to get the list of available camera + * devices. Note that even if an id is listed, open may fail if the device + * is disconnected between the calls to {@link #getCameraIdList} and + * {@link #openCamera}. + * + * @param cameraId The unique identifier of the camera device to open + * @param listener The listener for the camera. Must not be null. + * @param handler The handler to call the listener on. Must not be null. + * + * @throws CameraAccessException if the camera is disabled by device policy, + * or too many camera devices are already open, or the cameraId does not match + * any currently available camera device. + * + * @throws SecurityException if the application does not have permission to + * access the camera + * @throws IllegalArgumentException if listener or handler is null. + * + * @see #getCameraIdList + * @see android.app.admin.DevicePolicyManager#setCameraDisabled + */ + private void openCameraDeviceUserAsync(String cameraId, + CameraDevice.StateListener listener, Handler handler) + throws CameraAccessException { + try { + + synchronized (mLock) { + + ICameraDeviceUser cameraUser; + + android.hardware.camera2.impl.CameraDevice device = + new android.hardware.camera2.impl.CameraDevice( + cameraId, + listener, + handler); + + BinderHolder holder = new BinderHolder(); + mCameraService.connectDevice(device.getCallbacks(), + Integer.parseInt(cameraId), + mContext.getPackageName(), USE_CALLING_UID, holder); + cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); + + // TODO: factor out listener to be non-nested, then move setter to constructor + // For now, calling setRemoteDevice will fire initial + // onOpened/onUnconfigured callbacks. + device.setRemoteDevice(cameraUser); + } + + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " + + cameraId); + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + } + } + + /** + * Open a connection to a camera with the given ID. + * + *

    Use {@link #getCameraIdList} to get the list of available camera + * devices. Note that even if an id is listed, open may fail if the device + * is disconnected between the calls to {@link #getCameraIdList} and + * {@link #openCamera}.

    + * + *

    If the camera successfully opens after this function call returns, + * {@link CameraDevice.StateListener#onOpened} will be invoked with the + * newly opened {@link CameraDevice} in the unconfigured state.

    + * + *

    If the camera becomes disconnected during initialization + * after this function call returns, + * {@link CameraDevice.StateListener#onDisconnected} with a + * {@link CameraDevice} in the disconnected state (and + * {@link CameraDevice.StateListener#onOpened} will be skipped).

    + * + *

    If the camera fails to initialize after this function call returns, + * {@link CameraDevice.StateListener#onError} will be invoked with a + * {@link CameraDevice} in the error state (and + * {@link CameraDevice.StateListener#onOpened} will be skipped).

    + * + * @param cameraId + * The unique identifier of the camera device to open + * @param listener + * The listener which is invoked once the camera is opened + * @param handler + * The handler on which the listener should be invoked, or + * {@code null} to use the current thread's {@link android.os.Looper looper}. + * + * @throws CameraAccessException if the camera is disabled by device policy, + * or the camera has become or was disconnected. + * + * @throws IllegalArgumentException if cameraId or the listener was null, + * or the cameraId does not match any currently or previously available + * camera device. + * + * @throws SecurityException if the application does not have permission to + * access the camera + * + * @see #getCameraIdList + * @see android.app.admin.DevicePolicyManager#setCameraDisabled + */ + public void openCamera(String cameraId, final CameraDevice.StateListener listener, + Handler handler) + throws CameraAccessException { + + if (cameraId == null) { + throw new IllegalArgumentException("cameraId was null"); + } else if (listener == null) { + throw new IllegalArgumentException("listener was null"); + } else if (handler == null) { + if (Looper.myLooper() != null) { + handler = new Handler(); + } else { + throw new IllegalArgumentException( + "Looper doesn't exist in the calling thread"); + } + } + + openCameraDeviceUserAsync(cameraId, listener, handler); + } + + /** + * Interface for listening to camera devices becoming available or + * unavailable. + * + *

    Cameras become available when they are no longer in use, or when a new + * removable camera is connected. They become unavailable when some + * application or service starts using a camera, or when a removable camera + * is disconnected.

    + * + * @see addAvailabilityListener + */ + public static abstract class AvailabilityListener { + + /** + * A new camera has become available to use. + * + *

    The default implementation of this method does nothing.

    + * + * @param cameraId The unique identifier of the new camera. + */ + public void onCameraAvailable(String cameraId) { + // default empty implementation + } + + /** + * A previously-available camera has become unavailable for use. + * + *

    If an application had an active CameraDevice instance for the + * now-disconnected camera, that application will receive a + * {@link CameraDevice.StateListener#onDisconnected disconnection error}.

    + * + *

    The default implementation of this method does nothing.

    + * + * @param cameraId The unique identifier of the disconnected camera. + */ + public void onCameraUnavailable(String cameraId) { + // default empty implementation + } + } + + private ArrayList getOrCreateDeviceIdListLocked() throws CameraAccessException { + if (mDeviceIdList == null) { + int numCameras = 0; + + try { + numCameras = mCameraService.getNumberOfCameras(); + } catch(CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return null; + } + + mDeviceIdList = new ArrayList(); + CameraMetadataNative info = new CameraMetadataNative(); + for (int i = 0; i < numCameras; ++i) { + // Non-removable cameras use integers starting at 0 for their + // identifiers + boolean isDeviceSupported = false; + try { + mCameraService.getCameraCharacteristics(i, info); + if (!info.isEmpty()) { + isDeviceSupported = true; + } else { + throw new AssertionError("Expected to get non-empty characteristics"); + } + } catch(IllegalArgumentException e) { + // Got a BAD_VALUE from service, meaning that this + // device is not supported. + } catch(CameraRuntimeException e) { + throw e.asChecked(); + } catch(RemoteException e) { + // impossible + } + + if (isDeviceSupported) { + mDeviceIdList.add(String.valueOf(i)); + } + } + + } + return mDeviceIdList; + } + + // TODO: this class needs unit tests + // TODO: extract class into top level + private class CameraServiceListener extends ICameraServiceListener.Stub { + + // Keep up-to-date with ICameraServiceListener.h + + // Device physically unplugged + public static final int STATUS_NOT_PRESENT = 0; + // Device physically has been plugged in + // and the camera can be used exclusively + public static final int STATUS_PRESENT = 1; + // Device physically has been plugged in + // but it will not be connect-able until enumeration is complete + public static final int STATUS_ENUMERATING = 2; + // Camera is in use by another app and cannot be used exclusively + public static final int STATUS_NOT_AVAILABLE = 0x80000000; + + // Camera ID -> Status map + private final ArrayMap mDeviceStatus = new ArrayMap(); + + private static final String TAG = "CameraServiceListener"; + + @Override + public IBinder asBinder() { + return this; + } + + private boolean isAvailable(int status) { + switch (status) { + case STATUS_PRESENT: + return true; + default: + return false; + } + } + + private boolean validStatus(int status) { + switch (status) { + case STATUS_NOT_PRESENT: + case STATUS_PRESENT: + case STATUS_ENUMERATING: + case STATUS_NOT_AVAILABLE: + return true; + default: + return false; + } + } + + @Override + public void onStatusChanged(int status, int cameraId) throws RemoteException { + synchronized(CameraManager.this.mLock) { + + Log.v(TAG, + String.format("Camera id %d has status changed to 0x%x", cameraId, status)); + + final String id = String.valueOf(cameraId); + + if (!validStatus(status)) { + Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId, + status)); + return; + } + + Integer oldStatus = mDeviceStatus.put(id, status); + + if (oldStatus != null && oldStatus == status) { + Log.v(TAG, String.format( + "Device status changed to 0x%x, which is what it already was", + status)); + return; + } + + // TODO: consider abstracting out this state minimization + transition + // into a separate + // more easily testable class + // i.e. (new State()).addState(STATE_AVAILABLE) + // .addState(STATE_NOT_AVAILABLE) + // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), + // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) + // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); + // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); + + // Translate all the statuses to either 'available' or 'not available' + // available -> available => no new update + // not available -> not available => no new update + if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { + + Log.v(TAG, + String.format( + "Device status was previously available (%d), " + + " and is now again available (%d)" + + "so no new client visible update will be sent", + isAvailable(status), isAvailable(status))); + return; + } + + final int listenerCount = mListenerMap.size(); + for (int i = 0; i < listenerCount; i++) { + Handler handler = mListenerMap.valueAt(i); + final AvailabilityListener listener = mListenerMap.keyAt(i); + if (isAvailable(status)) { + handler.post( + new Runnable() { + @Override + public void run() { + listener.onCameraAvailable(id); + } + }); + } else { + handler.post( + new Runnable() { + @Override + public void run() { + listener.onCameraUnavailable(id); + } + }); + } + } // for + } // synchronized + } // onStatusChanged + } // CameraServiceListener +} // CameraManager diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java new file mode 100644 index 0000000000000000000000000000000000000000..1d6ff7db869bf2541041d84e22ae3edbeffa7516 --- /dev/null +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -0,0 +1,1251 @@ +/* + * 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.hardware.camera2; + +import android.hardware.camera2.impl.CameraMetadataNative; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * The base class for camera controls and information. + * + *

    + * This class defines the basic key/value map used for querying for camera + * characteristics or capture results, and for setting camera request + * parameters. + *

    + * + *

    + * All instances of CameraMetadata are immutable. The list of keys with {@link #getKeys()} + * never changes, nor do the values returned by any key with {@link #get} throughout + * the lifetime of the object. + *

    + * + * @see CameraDevice + * @see CameraManager + * @see CameraCharacteristics + **/ +public abstract class CameraMetadata { + + /** + * Set a camera metadata field to a value. The field definitions can be + * found in {@link CameraCharacteristics}, {@link CaptureResult}, and + * {@link CaptureRequest}. + * + * @param key The metadata field to write. + * @param value The value to set the field to, which must be of a matching + * type to the key. + * + * @hide + */ + protected CameraMetadata() { + } + + /** + * Get a camera metadata field value. + * + *

    The field definitions can be + * found in {@link CameraCharacteristics}, {@link CaptureResult}, and + * {@link CaptureRequest}.

    + * + *

    Querying the value for the same key more than once will return a value + * which is equal to the previous queried value.

    + * + * @throws IllegalArgumentException if the key was not valid + * + * @param key The metadata field to read. + * @return The value of that key, or {@code null} if the field is not set. + */ + public abstract T get(Key key); + + /** + * Returns a list of the keys contained in this map. + * + *

    The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.

    + * + *

    All values retrieved by a key from this list with {@link #get} are guaranteed to be + * non-{@code null}. Each key is only listed once in the list. The order of the keys + * is undefined.

    + * + * @return List of the keys contained in this map. + */ + public List> getKeys() { + return Collections.unmodifiableList(getKeysStatic(this.getClass(), this)); + } + + /** + * Return a list of all the Key that are declared as a field inside of the class + * {@code type}. + * + *

    + * Optionally, if {@code instance} is not null, then filter out any keys with null values. + *

    + */ + /*package*/ static ArrayList> getKeysStatic(Class type, + CameraMetadata instance) { + ArrayList> keyList = new ArrayList>(); + + Field[] fields = type.getDeclaredFields(); + for (Field field : fields) { + // Filter for Keys that are public + if (field.getType().isAssignableFrom(Key.class) && + (field.getModifiers() & Modifier.PUBLIC) != 0) { + Key key; + try { + key = (Key) field.get(instance); + } catch (IllegalAccessException e) { + throw new AssertionError("Can't get IllegalAccessException", e); + } catch (IllegalArgumentException e) { + throw new AssertionError("Can't get IllegalArgumentException", e); + } + if (instance == null || instance.get(key) != null) { + keyList.add(key); + } + } + } + + return keyList; + } + + public static class Key { + + private boolean mHasTag; + private int mTag; + private final Class mType; + private final String mName; + + /** + * @hide + */ + public Key(String name, Class type) { + if (name == null) { + throw new NullPointerException("Key needs a valid name"); + } else if (type == null) { + throw new NullPointerException("Type needs to be non-null"); + } + mName = name; + mType = type; + } + + public final String getName() { + return mName; + } + + @Override + public final int hashCode() { + return mName.hashCode(); + } + + @Override + @SuppressWarnings("unchecked") + public final boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof Key)) { + return false; + } + + Key lhs = (Key) o; + + return mName.equals(lhs.mName); + } + + /** + *

    + * Get the tag corresponding to this key. This enables insertion into the + * native metadata. + *

    + * + *

    This value is looked up the first time, and cached subsequently.

    + * + * @return The tag numeric value corresponding to the string + * + * @hide + */ + public final int getTag() { + if (!mHasTag) { + mTag = CameraMetadataNative.getTag(mName); + mHasTag = true; + } + return mTag; + } + + /** + * @hide + */ + public final Class getType() { + return mType; + } + } + + /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * The enum values below this point are generated from metadata + * definitions in /system/media/camera/docs. Do not modify by hand or + * modify the comment blocks at the start or end. + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ + + // + // Enumeration values for CameraCharacteristics#LENS_FACING + // + + /** + * @see CameraCharacteristics#LENS_FACING + */ + public static final int LENS_FACING_FRONT = 0; + + /** + * @see CameraCharacteristics#LENS_FACING + */ + public static final int LENS_FACING_BACK = 1; + + // + // Enumeration values for CameraCharacteristics#LED_AVAILABLE_LEDS + // + + /** + *

    + * android.led.transmit control is used + *

    + * @see CameraCharacteristics#LED_AVAILABLE_LEDS + * @hide + */ + public static final int LED_AVAILABLE_LEDS_TRANSMIT = 0; + + // + // Enumeration values for CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + // + + /** + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + */ + public static final int INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED = 0; + + /** + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + */ + public static final int INFO_SUPPORTED_HARDWARE_LEVEL_FULL = 1; + + // + // Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE + // + + /** + *

    + * Use the android.colorCorrection.transform matrix + * and android.colorCorrection.gains to do color conversion + *

    + * @see CaptureRequest#COLOR_CORRECTION_MODE + */ + public static final int COLOR_CORRECTION_MODE_TRANSFORM_MATRIX = 0; + + /** + *

    + * Must not slow down frame rate relative to raw + * bayer output + *

    + * @see CaptureRequest#COLOR_CORRECTION_MODE + */ + public static final int COLOR_CORRECTION_MODE_FAST = 1; + + /** + *

    + * Frame rate may be reduced by high + * quality + *

    + * @see CaptureRequest#COLOR_CORRECTION_MODE + */ + public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; + + // + // Enumeration values for CaptureRequest#CONTROL_AE_ANTIBANDING_MODE + // + + /** + * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE + */ + public static final int CONTROL_AE_ANTIBANDING_MODE_OFF = 0; + + /** + * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE + */ + public static final int CONTROL_AE_ANTIBANDING_MODE_50HZ = 1; + + /** + * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE + */ + public static final int CONTROL_AE_ANTIBANDING_MODE_60HZ = 2; + + /** + * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE + */ + public static final int CONTROL_AE_ANTIBANDING_MODE_AUTO = 3; + + // + // Enumeration values for CaptureRequest#CONTROL_AE_MODE + // + + /** + *

    + * Autoexposure is disabled; sensor.exposureTime, + * sensor.sensitivity and sensor.frameDuration are used + *

    + * @see CaptureRequest#CONTROL_AE_MODE + */ + public static final int CONTROL_AE_MODE_OFF = 0; + + /** + *

    + * Autoexposure is active, no flash + * control + *

    + * @see CaptureRequest#CONTROL_AE_MODE + */ + public static final int CONTROL_AE_MODE_ON = 1; + + /** + *

    + * if flash exists Autoexposure is active, auto + * flash control; flash may be fired when precapture + * trigger is activated, and for captures for which + * captureIntent = STILL_CAPTURE + *

    + * @see CaptureRequest#CONTROL_AE_MODE + */ + public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2; + + /** + *

    + * if flash exists Autoexposure is active, auto + * flash control for precapture trigger and always flash + * when captureIntent = STILL_CAPTURE + *

    + * @see CaptureRequest#CONTROL_AE_MODE + */ + public static final int CONTROL_AE_MODE_ON_ALWAYS_FLASH = 3; + + /** + *

    + * optional Automatic red eye reduction with flash. + * If deemed necessary, red eye reduction sequence should + * fire when precapture trigger is activated, and final + * flash should fire when captureIntent = + * STILL_CAPTURE + *

    + * @see CaptureRequest#CONTROL_AE_MODE + */ + public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; + + // + // Enumeration values for CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + // + + /** + *

    + * The trigger is idle. + *

    + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + */ + public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; + + /** + *

    + * The precapture metering sequence + * must be started. The exact effect of the precapture + * trigger depends on the current AE mode and + * state. + *

    + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + */ + public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; + + // + // Enumeration values for CaptureRequest#CONTROL_AF_MODE + // + + /** + *

    + * The 3A routines do not control the lens; + * android.lens.focusDistance is controlled by the + * application + *

    + * @see CaptureRequest#CONTROL_AF_MODE + */ + public static final int CONTROL_AF_MODE_OFF = 0; + + /** + *

    + * if lens is not fixed focus. + *

    + * Use android.lens.minimumFocusDistance to determine if lens + * is fixed focus In this mode, the lens does not move unless + * the autofocus trigger action is called. When that trigger + * is activated, AF must transition to ACTIVE_SCAN, then to + * the outcome of the scan (FOCUSED or + * NOT_FOCUSED). + *

    + * Triggering cancel AF resets the lens position to default, + * and sets the AF state to INACTIVE. + *

    + * @see CaptureRequest#CONTROL_AF_MODE + */ + public static final int CONTROL_AF_MODE_AUTO = 1; + + /** + *

    + * In this mode, the lens does not move unless the + * autofocus trigger action is called. + *

    + * When that trigger is activated, AF must transition to + * ACTIVE_SCAN, then to the outcome of the scan (FOCUSED or + * NOT_FOCUSED). Triggering cancel AF resets the lens + * position to default, and sets the AF state to + * INACTIVE. + *

    + * @see CaptureRequest#CONTROL_AF_MODE + */ + public static final int CONTROL_AF_MODE_MACRO = 2; + + /** + *

    + * In this mode, the AF algorithm modifies the lens + * position continually to attempt to provide a + * constantly-in-focus image stream. + *

    + * The focusing behavior should be suitable for good quality + * video recording; typically this means slower focus + * movement and no overshoots. When the AF trigger is not + * involved, the AF algorithm should start in INACTIVE state, + * and then transition into PASSIVE_SCAN and PASSIVE_FOCUSED + * states as appropriate. When the AF trigger is activated, + * the algorithm should immediately transition into + * AF_FOCUSED or AF_NOT_FOCUSED as appropriate, and lock the + * lens position until a cancel AF trigger is received. + *

    + * Once cancel is received, the algorithm should transition + * back to INACTIVE and resume passive scan. Note that this + * behavior is not identical to CONTINUOUS_PICTURE, since an + * ongoing PASSIVE_SCAN must immediately be + * canceled. + *

    + * @see CaptureRequest#CONTROL_AF_MODE + */ + public static final int CONTROL_AF_MODE_CONTINUOUS_VIDEO = 3; + + /** + *

    + * In this mode, the AF algorithm modifies the lens + * position continually to attempt to provide a + * constantly-in-focus image stream. + *

    + * The focusing behavior should be suitable for still image + * capture; typically this means focusing as fast as + * possible. When the AF trigger is not involved, the AF + * algorithm should start in INACTIVE state, and then + * transition into PASSIVE_SCAN and PASSIVE_FOCUSED states as + * appropriate as it attempts to maintain focus. When the AF + * trigger is activated, the algorithm should finish its + * PASSIVE_SCAN if active, and then transition into + * AF_FOCUSED or AF_NOT_FOCUSED as appropriate, and lock the + * lens position until a cancel AF trigger is received. + *

    + * When the AF cancel trigger is activated, the algorithm + * should transition back to INACTIVE and then act as if it + * has just been started. + *

    + * @see CaptureRequest#CONTROL_AF_MODE + */ + public static final int CONTROL_AF_MODE_CONTINUOUS_PICTURE = 4; + + /** + *

    + * Extended depth of field (digital focus). AF + * trigger is ignored, AF state should always be + * INACTIVE. + *

    + * @see CaptureRequest#CONTROL_AF_MODE + */ + public static final int CONTROL_AF_MODE_EDOF = 5; + + // + // Enumeration values for CaptureRequest#CONTROL_AF_TRIGGER + // + + /** + *

    + * The trigger is idle. + *

    + * @see CaptureRequest#CONTROL_AF_TRIGGER + */ + public static final int CONTROL_AF_TRIGGER_IDLE = 0; + + /** + *

    + * Autofocus must trigger now. + *

    + * @see CaptureRequest#CONTROL_AF_TRIGGER + */ + public static final int CONTROL_AF_TRIGGER_START = 1; + + /** + *

    + * Autofocus must return to initial + * state, and cancel any active trigger. + *

    + * @see CaptureRequest#CONTROL_AF_TRIGGER + */ + public static final int CONTROL_AF_TRIGGER_CANCEL = 2; + + // + // Enumeration values for CaptureRequest#CONTROL_AWB_MODE + // + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_OFF = 0; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_AUTO = 1; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_INCANDESCENT = 2; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_FLUORESCENT = 3; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_WARM_FLUORESCENT = 4; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_DAYLIGHT = 5; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_CLOUDY_DAYLIGHT = 6; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_TWILIGHT = 7; + + /** + * @see CaptureRequest#CONTROL_AWB_MODE + */ + public static final int CONTROL_AWB_MODE_SHADE = 8; + + // + // Enumeration values for CaptureRequest#CONTROL_CAPTURE_INTENT + // + + /** + *

    + * This request doesn't fall into the other + * categories. Default to preview-like + * behavior. + *

    + * @see CaptureRequest#CONTROL_CAPTURE_INTENT + */ + public static final int CONTROL_CAPTURE_INTENT_CUSTOM = 0; + + /** + *

    + * This request is for a preview-like usecase. The + * precapture trigger may be used to start off a metering + * w/flash sequence + *

    + * @see CaptureRequest#CONTROL_CAPTURE_INTENT + */ + public static final int CONTROL_CAPTURE_INTENT_PREVIEW = 1; + + /** + *

    + * This request is for a still capture-type + * usecase. + *

    + * @see CaptureRequest#CONTROL_CAPTURE_INTENT + */ + public static final int CONTROL_CAPTURE_INTENT_STILL_CAPTURE = 2; + + /** + *

    + * This request is for a video recording + * usecase. + *

    + * @see CaptureRequest#CONTROL_CAPTURE_INTENT + */ + public static final int CONTROL_CAPTURE_INTENT_VIDEO_RECORD = 3; + + /** + *

    + * This request is for a video snapshot (still + * image while recording video) usecase + *

    + * @see CaptureRequest#CONTROL_CAPTURE_INTENT + */ + public static final int CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT = 4; + + /** + *

    + * This request is for a ZSL usecase; the + * application will stream full-resolution images and + * reprocess one or several later for a final + * capture + *

    + * @see CaptureRequest#CONTROL_CAPTURE_INTENT + */ + public static final int CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG = 5; + + // + // Enumeration values for CaptureRequest#CONTROL_EFFECT_MODE + // + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_OFF = 0; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_MONO = 1; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_NEGATIVE = 2; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_SOLARIZE = 3; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_SEPIA = 4; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_POSTERIZE = 5; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_WHITEBOARD = 6; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_BLACKBOARD = 7; + + /** + * @see CaptureRequest#CONTROL_EFFECT_MODE + */ + public static final int CONTROL_EFFECT_MODE_AQUA = 8; + + // + // Enumeration values for CaptureRequest#CONTROL_MODE + // + + /** + *

    + * Full application control of pipeline. All 3A + * routines are disabled, no other settings in + * android.control.* have any effect + *

    + * @see CaptureRequest#CONTROL_MODE + */ + public static final int CONTROL_MODE_OFF = 0; + + /** + *

    + * Use settings for each individual 3A routine. + * Manual control of capture parameters is disabled. All + * controls in android.control.* besides sceneMode take + * effect + *

    + * @see CaptureRequest#CONTROL_MODE + */ + public static final int CONTROL_MODE_AUTO = 1; + + /** + *

    + * Use specific scene mode. Enabling this disables + * control.aeMode, control.awbMode and control.afMode + * controls; the HAL must ignore those settings while + * USE_SCENE_MODE is active (except for FACE_PRIORITY + * scene mode). Other control entries are still active. + * This setting can only be used if availableSceneModes != + * UNSUPPORTED + *

    + * @see CaptureRequest#CONTROL_MODE + */ + public static final int CONTROL_MODE_USE_SCENE_MODE = 2; + + // + // Enumeration values for CaptureRequest#CONTROL_SCENE_MODE + // + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_UNSUPPORTED = 0; + + /** + *

    + * if face detection support exists Use face + * detection data to drive 3A routines. If face detection + * statistics are disabled, should still operate correctly + * (but not return face detection statistics to the + * framework). + *

    + * Unlike the other scene modes, aeMode, awbMode, and afMode + * remain active when FACE_PRIORITY is set. This is due to + * compatibility concerns with the old camera + * API + *

    + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_ACTION = 2; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_PORTRAIT = 3; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_NIGHT = 5; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_THEATRE = 7; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_BEACH = 8; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_SNOW = 9; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_SUNSET = 10; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_STEADYPHOTO = 11; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_FIREWORKS = 12; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_SPORTS = 13; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_PARTY = 14; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_CANDLELIGHT = 15; + + /** + * @see CaptureRequest#CONTROL_SCENE_MODE + */ + public static final int CONTROL_SCENE_MODE_BARCODE = 16; + + // + // Enumeration values for CaptureRequest#EDGE_MODE + // + + /** + *

    + * No edge enhancement is applied + *

    + * @see CaptureRequest#EDGE_MODE + */ + public static final int EDGE_MODE_OFF = 0; + + /** + *

    + * Must not slow down frame rate relative to raw + * bayer output + *

    + * @see CaptureRequest#EDGE_MODE + */ + public static final int EDGE_MODE_FAST = 1; + + /** + *

    + * Frame rate may be reduced by high + * quality + *

    + * @see CaptureRequest#EDGE_MODE + */ + public static final int EDGE_MODE_HIGH_QUALITY = 2; + + // + // Enumeration values for CaptureRequest#FLASH_MODE + // + + /** + *

    + * Do not fire the flash for this + * capture + *

    + * @see CaptureRequest#FLASH_MODE + */ + public static final int FLASH_MODE_OFF = 0; + + /** + *

    + * if android.flash.available is true Fire flash + * for this capture based on firingPower, + * firingTime. + *

    + * @see CaptureRequest#FLASH_MODE + */ + public static final int FLASH_MODE_SINGLE = 1; + + /** + *

    + * if android.flash.available is true Flash + * continuously on, power set by + * firingPower + *

    + * @see CaptureRequest#FLASH_MODE + */ + public static final int FLASH_MODE_TORCH = 2; + + // + // Enumeration values for CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE + // + + /** + * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE + */ + public static final int LENS_OPTICAL_STABILIZATION_MODE_OFF = 0; + + /** + * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE + */ + public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1; + + // + // Enumeration values for CaptureRequest#NOISE_REDUCTION_MODE + // + + /** + *

    + * No noise reduction is applied + *

    + * @see CaptureRequest#NOISE_REDUCTION_MODE + */ + public static final int NOISE_REDUCTION_MODE_OFF = 0; + + /** + *

    + * Must not slow down frame rate relative to raw + * bayer output + *

    + * @see CaptureRequest#NOISE_REDUCTION_MODE + */ + public static final int NOISE_REDUCTION_MODE_FAST = 1; + + /** + *

    + * May slow down frame rate to provide highest + * quality + *

    + * @see CaptureRequest#NOISE_REDUCTION_MODE + */ + public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; + + // + // Enumeration values for CaptureRequest#STATISTICS_FACE_DETECT_MODE + // + + /** + * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE + */ + public static final int STATISTICS_FACE_DETECT_MODE_OFF = 0; + + /** + *

    + * Optional Return rectangle and confidence + * only + *

    + * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE + */ + public static final int STATISTICS_FACE_DETECT_MODE_SIMPLE = 1; + + /** + *

    + * Optional Return all face + * metadata + *

    + * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE + */ + public static final int STATISTICS_FACE_DETECT_MODE_FULL = 2; + + // + // Enumeration values for CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE + // + + /** + * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE + */ + public static final int STATISTICS_LENS_SHADING_MAP_MODE_OFF = 0; + + /** + * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE + */ + public static final int STATISTICS_LENS_SHADING_MAP_MODE_ON = 1; + + // + // Enumeration values for CaptureRequest#TONEMAP_MODE + // + + /** + *

    + * Use the tone mapping curve specified in + * android.tonemap.curve + *

    + * @see CaptureRequest#TONEMAP_MODE + */ + public static final int TONEMAP_MODE_CONTRAST_CURVE = 0; + + /** + *

    + * Must not slow down frame rate relative to raw + * bayer output + *

    + * @see CaptureRequest#TONEMAP_MODE + */ + public static final int TONEMAP_MODE_FAST = 1; + + /** + *

    + * Frame rate may be reduced by high + * quality + *

    + * @see CaptureRequest#TONEMAP_MODE + */ + public static final int TONEMAP_MODE_HIGH_QUALITY = 2; + + // + // Enumeration values for CaptureResult#CONTROL_AE_STATE + // + + /** + *

    + * AE is off. When a camera device is opened, it starts in + * this state. + *

    + * @see CaptureResult#CONTROL_AE_STATE + */ + public static final int CONTROL_AE_STATE_INACTIVE = 0; + + /** + *

    + * AE doesn't yet have a good set of control values + * for the current scene + *

    + * @see CaptureResult#CONTROL_AE_STATE + */ + public static final int CONTROL_AE_STATE_SEARCHING = 1; + + /** + *

    + * AE has a good set of control values for the + * current scene + *

    + * @see CaptureResult#CONTROL_AE_STATE + */ + public static final int CONTROL_AE_STATE_CONVERGED = 2; + + /** + *

    + * AE has been locked (aeMode = + * LOCKED) + *

    + * @see CaptureResult#CONTROL_AE_STATE + */ + public static final int CONTROL_AE_STATE_LOCKED = 3; + + /** + *

    + * AE has a good set of control values, but flash + * needs to be fired for good quality still + * capture + *

    + * @see CaptureResult#CONTROL_AE_STATE + */ + public static final int CONTROL_AE_STATE_FLASH_REQUIRED = 4; + + /** + *

    + * AE has been asked to do a precapture sequence + * (through the + * trigger_action(CAMERA2_TRIGGER_PRECAPTURE_METERING) + * call), and is currently executing it. Once PRECAPTURE + * completes, AE will transition to CONVERGED or + * FLASH_REQUIRED as appropriate + *

    + * @see CaptureResult#CONTROL_AE_STATE + */ + public static final int CONTROL_AE_STATE_PRECAPTURE = 5; + + // + // Enumeration values for CaptureResult#CONTROL_AF_STATE + // + + /** + *

    + * AF off or has not yet tried to scan/been asked + * to scan. When a camera device is opened, it starts in + * this state. + *

    + * @see CaptureResult#CONTROL_AF_STATE + */ + public static final int CONTROL_AF_STATE_INACTIVE = 0; + + /** + *

    + * if CONTINUOUS_* modes are supported. AF is + * currently doing an AF scan initiated by a continuous + * autofocus mode + *

    + * @see CaptureResult#CONTROL_AF_STATE + */ + public static final int CONTROL_AF_STATE_PASSIVE_SCAN = 1; + + /** + *

    + * if CONTINUOUS_* modes are supported. AF currently + * believes it is in focus, but may restart scanning at + * any time. + *

    + * @see CaptureResult#CONTROL_AF_STATE + */ + public static final int CONTROL_AF_STATE_PASSIVE_FOCUSED = 2; + + /** + *

    + * if AUTO or MACRO modes are supported. AF is doing + * an AF scan because it was triggered by AF + * trigger + *

    + * @see CaptureResult#CONTROL_AF_STATE + */ + public static final int CONTROL_AF_STATE_ACTIVE_SCAN = 3; + + /** + *

    + * if any AF mode besides OFF is supported. AF + * believes it is focused correctly and is + * locked + *

    + * @see CaptureResult#CONTROL_AF_STATE + */ + public static final int CONTROL_AF_STATE_FOCUSED_LOCKED = 4; + + /** + *

    + * if any AF mode besides OFF is supported. AF has + * failed to focus successfully and is + * locked + *

    + * @see CaptureResult#CONTROL_AF_STATE + */ + public static final int CONTROL_AF_STATE_NOT_FOCUSED_LOCKED = 5; + + /** + *

    + * if CONTINUOUS_* modes are supported. AF finished a + * passive scan without finding focus, and may restart + * scanning at any time. + *

    + * @see CaptureResult#CONTROL_AF_STATE + */ + public static final int CONTROL_AF_STATE_PASSIVE_UNFOCUSED = 6; + + // + // Enumeration values for CaptureResult#CONTROL_AWB_STATE + // + + /** + *

    + * AWB is not in auto mode. When a camera device is opened, it + * starts in this state. + *

    + * @see CaptureResult#CONTROL_AWB_STATE + */ + public static final int CONTROL_AWB_STATE_INACTIVE = 0; + + /** + *

    + * AWB doesn't yet have a good set of control + * values for the current scene + *

    + * @see CaptureResult#CONTROL_AWB_STATE + */ + public static final int CONTROL_AWB_STATE_SEARCHING = 1; + + /** + *

    + * AWB has a good set of control values for the + * current scene + *

    + * @see CaptureResult#CONTROL_AWB_STATE + */ + public static final int CONTROL_AWB_STATE_CONVERGED = 2; + + /** + *

    + * AE has been locked (aeMode = + * LOCKED) + *

    + * @see CaptureResult#CONTROL_AWB_STATE + */ + public static final int CONTROL_AWB_STATE_LOCKED = 3; + + // + // Enumeration values for CaptureResult#FLASH_STATE + // + + /** + *

    + * No flash on camera + *

    + * @see CaptureResult#FLASH_STATE + */ + public static final int FLASH_STATE_UNAVAILABLE = 0; + + /** + *

    + * if android.flash.available is true Flash is + * charging and cannot be fired + *

    + * @see CaptureResult#FLASH_STATE + */ + public static final int FLASH_STATE_CHARGING = 1; + + /** + *

    + * if android.flash.available is true Flash is + * ready to fire + *

    + * @see CaptureResult#FLASH_STATE + */ + public static final int FLASH_STATE_READY = 2; + + /** + *

    + * if android.flash.available is true Flash fired + * for this capture + *

    + * @see CaptureResult#FLASH_STATE + */ + public static final int FLASH_STATE_FIRED = 3; + + // + // Enumeration values for CaptureResult#LENS_STATE + // + + /** + * @see CaptureResult#LENS_STATE + */ + public static final int LENS_STATE_STATIONARY = 0; + + /** + * @see CaptureResult#LENS_STATE + */ + public static final int LENS_STATE_MOVING = 1; + + // + // Enumeration values for CaptureResult#STATISTICS_SCENE_FLICKER + // + + /** + * @see CaptureResult#STATISTICS_SCENE_FLICKER + */ + public static final int STATISTICS_SCENE_FLICKER_NONE = 0; + + /** + * @see CaptureResult#STATISTICS_SCENE_FLICKER + */ + public static final int STATISTICS_SCENE_FLICKER_50HZ = 1; + + /** + * @see CaptureResult#STATISTICS_SCENE_FLICKER + */ + public static final int STATISTICS_SCENE_FLICKER_60HZ = 2; + + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * End generated code + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ + +} diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java new file mode 100644 index 0000000000000000000000000000000000000000..3b408cfcb0f05a7822739edf67836837c7011c75 --- /dev/null +++ b/core/java/android/hardware/camera2/CaptureFailure.java @@ -0,0 +1,144 @@ +/* + * 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.hardware.camera2; + +import android.hardware.camera2.CameraDevice.CaptureListener; + +/** + * A report of failed capture for a single image capture from the image sensor. + * + *

    CaptureFailures are produced by a {@link CameraDevice} if processing a + * {@link CaptureRequest} fails, either partially or fully. Use {@link #getReason} + * to determine the specific nature of the failed capture.

    + * + *

    Receiving a CaptureFailure means that the metadata associated with that frame number + * has been dropped -- no {@link CaptureResult} with the same frame number will be + * produced.

    + */ +public class CaptureFailure { + /** + * The {@link CaptureResult} has been dropped this frame only due to an error + * in the framework. + * + * @see #getReason() + */ + public static final int REASON_ERROR = 0; + + /** + * The capture has failed due to a {@link CameraDevice#flush} call from the application. + * + * @see #getReason() + */ + public static final int REASON_FLUSHED = 1; + + private final CaptureRequest mRequest; + private final int mReason; + private final boolean mDropped; + private final int mSequenceId; + private final int mFrameNumber; + + /** + * @hide + */ + public CaptureFailure(CaptureRequest request, int reason, boolean dropped, int sequenceId, + int frameNumber) { + mRequest = request; + mReason = reason; + mDropped = dropped; + mSequenceId = sequenceId; + mFrameNumber = frameNumber; + } + + /** + * Get the request associated with this failed capture. + * + *

    Whenever a request is unsuccessfully captured, with + * {@link CameraDevice.CaptureListener#onCaptureFailed}, + * the {@code failed capture}'s {@code getRequest()} will return that {@code request}. + *

    + * + *

    In particular, + *

    cameraDevice.capture(someRequest, new CaptureListener() {
    +     *     {@literal @}Override
    +     *     void onCaptureFailed(CaptureRequest myRequest, CaptureFailure myFailure) {
    +     *         assert(myFailure.getRequest.equals(myRequest) == true);
    +     *     }
    +     * };
    +     * 
    + *

    + * + * @return The request associated with this failed capture. Never {@code null}. + */ + public CaptureRequest getRequest() { + return mRequest; + } + + /** + * Get the frame number associated with this failed capture. + * + *

    Whenever a request has been processed, regardless of failed capture or success, + * it gets a unique frame number assigned to its future result/failed capture.

    + * + *

    This value monotonically increments, starting with 0, + * for every new result or failure; and the scope is the lifetime of the + * {@link CameraDevice}.

    + * + * @return int frame number + */ + public int getFrameNumber() { + return mFrameNumber; + } + + /** + * Determine why the request was dropped, whether due to an error or to a user + * action. + * + * @return int One of {@code REASON_*} integer constants. + * + * @see #REASON_ERROR + * @see #REASON_FLUSHED + */ + public int getReason() { + return mReason; + } + + /** + * Determine if the image was captured from the camera. + * + *

    If the image was not captured, no image buffers will be available. + * If the image was captured, then image buffers may be available.

    + * + * @return boolean True if the image was captured, false otherwise. + */ + public boolean wasImageCaptured() { + return !mDropped; + } + + /** + * The sequence ID for this failed capture that was returned by the + * {@link CameraDevice#capture} family of functions. + * + *

    The sequence ID is a unique monotonically increasing value starting from 0, + * incremented every time a new group of requests is submitted to the CameraDevice.

    + * + * @return int The ID for the sequence of requests that this capture failure is the result of + * + * @see CameraDevice.CaptureListener#onCaptureSequenceCompleted + */ + public int getSequenceId() { + return mSequenceId; + } +} diff --git a/core/java/android/hardware/camera2/CaptureRequest.aidl b/core/java/android/hardware/camera2/CaptureRequest.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0b7d5ba243aef512a216cd03fd9e58996b304b59 --- /dev/null +++ b/core/java/android/hardware/camera2/CaptureRequest.aidl @@ -0,0 +1,20 @@ +/* + * 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.hardware.camera2; + +/** @hide */ +parcelable CaptureRequest; diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..898f1232b832edb2db44745b415554af6cb51cb7 --- /dev/null +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -0,0 +1,1126 @@ +/* + * 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.hardware.camera2; + +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.CameraDevice.CaptureListener; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.Surface; + +import java.util.HashSet; +import java.util.Objects; + + +/** + *

    An immutable package of settings and outputs needed to capture a single + * image from the camera device.

    + * + *

    Contains the configuration for the capture hardware (sensor, lens, flash), + * the processing pipeline, the control algorithms, and the output buffers. Also + * contains the list of target Surfaces to send image data to for this + * capture.

    + * + *

    CaptureRequests can be created by using a {@link Builder} instance, + * obtained by calling {@link CameraDevice#createCaptureRequest}

    + * + *

    CaptureRequests are given to {@link CameraDevice#capture} or + * {@link CameraDevice#setRepeatingRequest} to capture images from a camera.

    + * + *

    Each request can specify a different subset of target Surfaces for the + * camera to send the captured data to. All the surfaces used in a request must + * be part of the surface list given to the last call to + * {@link CameraDevice#configureOutputs}, when the request is submitted to the + * camera device.

    + * + *

    For example, a request meant for repeating preview might only include the + * Surface for the preview SurfaceView or SurfaceTexture, while a + * high-resolution still capture would also include a Surface from a ImageReader + * configured for high-resolution JPEG images.

    + * + * @see CameraDevice#capture + * @see CameraDevice#setRepeatingRequest + * @see CameraDevice#createCaptureRequest + */ +public final class CaptureRequest extends CameraMetadata implements Parcelable { + + private final HashSet mSurfaceSet; + private final CameraMetadataNative mSettings; + + private Object mUserTag; + + /** + * Construct empty request. + * + * Used by Binder to unparcel this object only. + */ + private CaptureRequest() { + mSettings = new CameraMetadataNative(); + mSurfaceSet = new HashSet(); + } + + /** + * Clone from source capture request. + * + * Used by the Builder to create an immutable copy. + */ + @SuppressWarnings("unchecked") + private CaptureRequest(CaptureRequest source) { + mSettings = new CameraMetadataNative(source.mSettings); + mSurfaceSet = (HashSet) source.mSurfaceSet.clone(); + mUserTag = source.mUserTag; + } + + /** + * Take ownership of passed-in settings. + * + * Used by the Builder to create a mutable CaptureRequest. + */ + private CaptureRequest(CameraMetadataNative settings) { + mSettings = settings; + mSurfaceSet = new HashSet(); + } + + @SuppressWarnings("unchecked") + @Override + public T get(Key key) { + return mSettings.get(key); + } + + /** + * Retrieve the tag for this request, if any. + * + *

    This tag is not used for anything by the camera device, but can be + * used by an application to easily identify a CaptureRequest when it is + * returned by + * {@link CameraDevice.CaptureListener#onCaptureCompleted CaptureListener.onCaptureCompleted} + *

    + * + * @return the last tag Object set on this request, or {@code null} if + * no tag has been set. + * @see Builder#setTag + */ + public Object getTag() { + return mUserTag; + } + + /** + * Determine whether this CaptureRequest is equal to another CaptureRequest. + * + *

    A request is considered equal to another is if it's set of key/values is equal, it's + * list of output surfaces is equal, and the user tag is equal.

    + * + * @param other Another instance of CaptureRequest. + * + * @return True if the requests are the same, false otherwise. + */ + @Override + public boolean equals(Object other) { + return other instanceof CaptureRequest + && equals((CaptureRequest)other); + } + + private boolean equals(CaptureRequest other) { + return other != null + && Objects.equals(mUserTag, other.mUserTag) + && mSurfaceSet.equals(other.mSurfaceSet) + && mSettings.equals(other.mSettings); + } + + @Override + public int hashCode() { + return mSettings.hashCode(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public CaptureRequest createFromParcel(Parcel in) { + CaptureRequest request = new CaptureRequest(); + request.readFromParcel(in); + + return request; + } + + @Override + public CaptureRequest[] newArray(int size) { + return new CaptureRequest[size]; + } + }; + + /** + * Expand this object from a Parcel. + * Hidden since this breaks the immutability of CaptureRequest, but is + * needed to receive CaptureRequests with aidl. + * + * @param in The parcel from which the object should be read + * @hide + */ + public void readFromParcel(Parcel in) { + mSettings.readFromParcel(in); + + mSurfaceSet.clear(); + + Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader()); + + if (parcelableArray == null) { + return; + } + + for (Parcelable p : parcelableArray) { + Surface s = (Surface) p; + mSurfaceSet.add(s); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mSettings.writeToParcel(dest, flags); + dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags); + } + + /** + * A builder for capture requests. + * + *

    To obtain a builder instance, use the + * {@link CameraDevice#createCaptureRequest} method, which initializes the + * request fields to one of the templates defined in {@link CameraDevice}. + * + * @see CameraDevice#createCaptureRequest + * @see #TEMPLATE_PREVIEW + * @see #TEMPLATE_RECORD + * @see #TEMPLATE_STILL_CAPTURE + * @see #TEMPLATE_VIDEO_SNAPSHOT + * @see #TEMPLATE_MANUAL + */ + public final static class Builder { + + private final CaptureRequest mRequest; + + /** + * Initialize the builder using the template; the request takes + * ownership of the template. + * + * @hide + */ + public Builder(CameraMetadataNative template) { + mRequest = new CaptureRequest(template); + } + + /** + *

    Add a surface to the list of targets for this request

    + * + *

    The Surface added must be one of the surfaces included in the most + * recent call to {@link CameraDevice#configureOutputs}, when the + * request is given to the camera device.

    + * + *

    Adding a target more than once has no effect.

    + * + * @param outputTarget Surface to use as an output target for this request + */ + public void addTarget(Surface outputTarget) { + mRequest.mSurfaceSet.add(outputTarget); + } + + /** + *

    Remove a surface from the list of targets for this request.

    + * + *

    Removing a target that is not currently added has no effect.

    + * + * @param outputTarget Surface to use as an output target for this request + */ + public void removeTarget(Surface outputTarget) { + mRequest.mSurfaceSet.remove(outputTarget); + } + + /** + * Set a capture request field to a value. The field definitions can be + * found in {@link CaptureRequest}. + * + * @param key The metadata field to write. + * @param value The value to set the field to, which must be of a matching + * type to the key. + */ + public void set(Key key, T value) { + mRequest.mSettings.set(key, value); + } + + /** + * Get a capture request field value. The field definitions can be + * found in {@link CaptureRequest}. + * + * @throws IllegalArgumentException if the key was not valid + * + * @param key The metadata field to read. + * @return The value of that key, or {@code null} if the field is not set. + */ + public T get(Key key) { + return mRequest.mSettings.get(key); + } + + /** + * Set a tag for this request. + * + *

    This tag is not used for anything by the camera device, but can be + * used by an application to easily identify a CaptureRequest when it is + * returned by + * {@link CameraDevice.CaptureListener#onCaptureCompleted CaptureListener.onCaptureCompleted} + * + * @param tag an arbitrary Object to store with this request + * @see CaptureRequest#getTag + */ + public void setTag(Object tag) { + mRequest.mUserTag = tag; + } + + /** + * Build a request using the current target Surfaces and settings. + * + * @return A new capture request instance, ready for submission to the + * camera device. + */ + public CaptureRequest build() { + return new CaptureRequest(mRequest); + } + + + /** + * @hide + */ + public boolean isEmpty() { + return mRequest.mSettings.isEmpty(); + } + + } + + /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * The key entries below this point are generated from metadata + * definitions in /system/media/camera/docs. Do not modify by hand or + * modify the comment blocks at the start or end. + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ + + /** + *

    + * When android.control.awbMode is not OFF, TRANSFORM_MATRIX + * should be ignored. + *

    + * @see #COLOR_CORRECTION_MODE_TRANSFORM_MATRIX + * @see #COLOR_CORRECTION_MODE_FAST + * @see #COLOR_CORRECTION_MODE_HIGH_QUALITY + */ + public static final Key COLOR_CORRECTION_MODE = + new Key("android.colorCorrection.mode", int.class); + + /** + *

    + * A color transform matrix to use to transform + * from sensor RGB color space to output linear sRGB color space + *

    + *

    + * This matrix is either set by HAL when the request + * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * directly by the application in the request when the + * android.colorCorrection.mode is TRANSFORM_MATRIX. + *

    + * In the latter case, the HAL may round the matrix to account + * for precision issues; the final rounded matrix should be + * reported back in this matrix result metadata. + *

    + */ + public static final Key COLOR_CORRECTION_TRANSFORM = + new Key("android.colorCorrection.transform", Rational[].class); + + /** + *

    + * Gains applying to Bayer color channels for + * white-balance + *

    + *

    + * The 4-channel white-balance gains are defined in + * the order of [R G_even G_odd B], where G_even is the gain + * for green pixels on even rows of the output, and G_odd + * is the gain for greenpixels on the odd rows. if a HAL + * does not support a separate gain for even/odd green channels, + * it should use the G_even value,and write G_odd equal to + * G_even in the output result metadata. + *

    + * This array is either set by HAL when the request + * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * directly by the application in the request when the + * android.colorCorrection.mode is TRANSFORM_MATRIX. + *

    + * The ouput should be the gains actually applied by the HAL to + * the current frame. + *

    + */ + public static final Key COLOR_CORRECTION_GAINS = + new Key("android.colorCorrection.gains", float[].class); + + /** + *

    + * Enum for controlling + * antibanding + *

    + * @see #CONTROL_AE_ANTIBANDING_MODE_OFF + * @see #CONTROL_AE_ANTIBANDING_MODE_50HZ + * @see #CONTROL_AE_ANTIBANDING_MODE_60HZ + * @see #CONTROL_AE_ANTIBANDING_MODE_AUTO + */ + public static final Key CONTROL_AE_ANTIBANDING_MODE = + new Key("android.control.aeAntibandingMode", int.class); + + /** + *

    + * Adjustment to AE target image + * brightness + *

    + *

    + * For example, if EV step is 0.333, '6' will mean an + * exposure compensation of +2 EV; -3 will mean an exposure + * compensation of -1 + *

    + */ + public static final Key CONTROL_AE_EXPOSURE_COMPENSATION = + new Key("android.control.aeExposureCompensation", int.class); + + /** + *

    + * Whether AE is currently locked to its latest + * calculated values + *

    + *

    + * Note that even when AE is locked, the flash may be + * fired if the AE mode is ON_AUTO_FLASH / ON_ALWAYS_FLASH / + * ON_AUTO_FLASH_REDEYE. + *

    + */ + public static final Key CONTROL_AE_LOCK = + new Key("android.control.aeLock", boolean.class); + + /** + *

    + * Whether AE is currently updating the sensor + * exposure and sensitivity fields + *

    + *

    + * Only effective if android.control.mode = + * AUTO + *

    + * @see #CONTROL_AE_MODE_OFF + * @see #CONTROL_AE_MODE_ON + * @see #CONTROL_AE_MODE_ON_AUTO_FLASH + * @see #CONTROL_AE_MODE_ON_ALWAYS_FLASH + * @see #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE + */ + public static final Key CONTROL_AE_MODE = + new Key("android.control.aeMode", int.class); + + /** + *

    + * List of areas to use for + * metering + *

    + *

    + * Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined inclusive of the + * specified coordinates. + *

    + * The coordinate system is based on the active pixel array, + * with (0,0) being the top-left pixel in the active pixel array, and + * (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1) being the + * bottom-right pixel in the active pixel array. The weight + * should be nonnegative. + *

    + * If all regions have 0 weight, then no specific metering area + * needs to be used by the HAL. If the metering region is + * outside the current android.scaler.cropRegion, the HAL + * should ignore the sections outside the region and output the + * used sections in the frame metadata + *

    + */ + public static final Key CONTROL_AE_REGIONS = + new Key("android.control.aeRegions", int[].class); + + /** + *

    + * Range over which fps can be adjusted to + * maintain exposure + *

    + *

    + * Only constrains AE algorithm, not manual control + * of android.sensor.exposureTime + *

    + */ + public static final Key CONTROL_AE_TARGET_FPS_RANGE = + new Key("android.control.aeTargetFpsRange", int[].class); + + /** + *

    + * Whether the HAL must trigger precapture + * metering. + *

    + *

    + * This entry is normally set to IDLE, or is not + * included at all in the request settings. When included and + * set to START, the HAL must trigger the autoexposure + * precapture metering sequence. + *

    + * The effect of AE precapture trigger depends on the current + * AE mode and state; see the camera HAL device v3 header for + * details. + *

    + * @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE + * @see #CONTROL_AE_PRECAPTURE_TRIGGER_START + */ + public static final Key CONTROL_AE_PRECAPTURE_TRIGGER = + new Key("android.control.aePrecaptureTrigger", int.class); + + /** + *

    + * Whether AF is currently enabled, and what + * mode it is set to + *

    + * @see #CONTROL_AF_MODE_OFF + * @see #CONTROL_AF_MODE_AUTO + * @see #CONTROL_AF_MODE_MACRO + * @see #CONTROL_AF_MODE_CONTINUOUS_VIDEO + * @see #CONTROL_AF_MODE_CONTINUOUS_PICTURE + * @see #CONTROL_AF_MODE_EDOF + */ + public static final Key CONTROL_AF_MODE = + new Key("android.control.afMode", int.class); + + /** + *

    + * List of areas to use for focus + * estimation + *

    + *

    + * Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined inclusive of the + * specified coordinates. + *

    + * The coordinate system is based on the active pixel array, + * with (0,0) being the top-left pixel in the active pixel array, and + * (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1) being the + * bottom-right pixel in the active pixel array. The weight + * should be nonnegative. + *

    + * If all regions have 0 weight, then no specific focus area + * needs to be used by the HAL. If the focusing region is + * outside the current android.scaler.cropRegion, the HAL + * should ignore the sections outside the region and output the + * used sections in the frame metadata + *

    + */ + public static final Key CONTROL_AF_REGIONS = + new Key("android.control.afRegions", int[].class); + + /** + *

    + * Whether the HAL must trigger autofocus. + *

    + *

    + * This entry is normally set to IDLE, or is not + * included at all in the request settings. + *

    + * When included and set to START, the HAL must trigger the + * autofocus algorithm. The effect of AF trigger depends on the + * current AF mode and state; see the camera HAL device v3 + * header for details. When set to CANCEL, the HAL must cancel + * any active trigger, and return to initial AF state. + *

    + * @see #CONTROL_AF_TRIGGER_IDLE + * @see #CONTROL_AF_TRIGGER_START + * @see #CONTROL_AF_TRIGGER_CANCEL + */ + public static final Key CONTROL_AF_TRIGGER = + new Key("android.control.afTrigger", int.class); + + /** + *

    + * Whether AWB is currently locked to its + * latest calculated values + *

    + *

    + * Note that AWB lock is only meaningful for AUTO + * mode; in other modes, AWB is already fixed to a specific + * setting + *

    + */ + public static final Key CONTROL_AWB_LOCK = + new Key("android.control.awbLock", boolean.class); + + /** + *

    + * Whether AWB is currently setting the color + * transform fields, and what its illumination target + * is + *

    + *

    + * [BC - AWB lock,AWB modes] + *

    + * @see #CONTROL_AWB_MODE_OFF + * @see #CONTROL_AWB_MODE_AUTO + * @see #CONTROL_AWB_MODE_INCANDESCENT + * @see #CONTROL_AWB_MODE_FLUORESCENT + * @see #CONTROL_AWB_MODE_WARM_FLUORESCENT + * @see #CONTROL_AWB_MODE_DAYLIGHT + * @see #CONTROL_AWB_MODE_CLOUDY_DAYLIGHT + * @see #CONTROL_AWB_MODE_TWILIGHT + * @see #CONTROL_AWB_MODE_SHADE + */ + public static final Key CONTROL_AWB_MODE = + new Key("android.control.awbMode", int.class); + + /** + *

    + * List of areas to use for illuminant + * estimation + *

    + *

    + * Only used in AUTO mode. + *

    + * Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined inclusive of the + * specified coordinates. + *

    + * The coordinate system is based on the active pixel array, + * with (0,0) being the top-left pixel in the active pixel array, and + * (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1) being the + * bottom-right pixel in the active pixel array. The weight + * should be nonnegative. + *

    + * If all regions have 0 weight, then no specific metering area + * needs to be used by the HAL. If the metering region is + * outside the current android.scaler.cropRegion, the HAL + * should ignore the sections outside the region and output the + * used sections in the frame metadata + *

    + */ + public static final Key CONTROL_AWB_REGIONS = + new Key("android.control.awbRegions", int[].class); + + /** + *

    + * Information to 3A routines about the purpose + * of this capture, to help decide optimal 3A + * strategy + *

    + *

    + * Only used if android.control.mode != OFF. + *

    + * @see #CONTROL_CAPTURE_INTENT_CUSTOM + * @see #CONTROL_CAPTURE_INTENT_PREVIEW + * @see #CONTROL_CAPTURE_INTENT_STILL_CAPTURE + * @see #CONTROL_CAPTURE_INTENT_VIDEO_RECORD + * @see #CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT + * @see #CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG + */ + public static final Key CONTROL_CAPTURE_INTENT = + new Key("android.control.captureIntent", int.class); + + /** + *

    + * Whether any special color effect is in use. + * Only used if android.control.mode != OFF + *

    + * @see #CONTROL_EFFECT_MODE_OFF + * @see #CONTROL_EFFECT_MODE_MONO + * @see #CONTROL_EFFECT_MODE_NEGATIVE + * @see #CONTROL_EFFECT_MODE_SOLARIZE + * @see #CONTROL_EFFECT_MODE_SEPIA + * @see #CONTROL_EFFECT_MODE_POSTERIZE + * @see #CONTROL_EFFECT_MODE_WHITEBOARD + * @see #CONTROL_EFFECT_MODE_BLACKBOARD + * @see #CONTROL_EFFECT_MODE_AQUA + */ + public static final Key CONTROL_EFFECT_MODE = + new Key("android.control.effectMode", int.class); + + /** + *

    + * Overall mode of 3A control + * routines + *

    + * @see #CONTROL_MODE_OFF + * @see #CONTROL_MODE_AUTO + * @see #CONTROL_MODE_USE_SCENE_MODE + */ + public static final Key CONTROL_MODE = + new Key("android.control.mode", int.class); + + /** + *

    + * Which scene mode is active when + * android.control.mode = SCENE_MODE + *

    + * @see #CONTROL_SCENE_MODE_UNSUPPORTED + * @see #CONTROL_SCENE_MODE_FACE_PRIORITY + * @see #CONTROL_SCENE_MODE_ACTION + * @see #CONTROL_SCENE_MODE_PORTRAIT + * @see #CONTROL_SCENE_MODE_LANDSCAPE + * @see #CONTROL_SCENE_MODE_NIGHT + * @see #CONTROL_SCENE_MODE_NIGHT_PORTRAIT + * @see #CONTROL_SCENE_MODE_THEATRE + * @see #CONTROL_SCENE_MODE_BEACH + * @see #CONTROL_SCENE_MODE_SNOW + * @see #CONTROL_SCENE_MODE_SUNSET + * @see #CONTROL_SCENE_MODE_STEADYPHOTO + * @see #CONTROL_SCENE_MODE_FIREWORKS + * @see #CONTROL_SCENE_MODE_SPORTS + * @see #CONTROL_SCENE_MODE_PARTY + * @see #CONTROL_SCENE_MODE_CANDLELIGHT + * @see #CONTROL_SCENE_MODE_BARCODE + */ + public static final Key CONTROL_SCENE_MODE = + new Key("android.control.sceneMode", int.class); + + /** + *

    + * Whether video stabilization is + * active + *

    + *

    + * If enabled, video stabilization can modify the + * android.scaler.cropRegion to keep the video stream + * stabilized + *

    + */ + public static final Key CONTROL_VIDEO_STABILIZATION_MODE = + new Key("android.control.videoStabilizationMode", boolean.class); + + /** + *

    + * Operation mode for edge + * enhancement + *

    + * @see #EDGE_MODE_OFF + * @see #EDGE_MODE_FAST + * @see #EDGE_MODE_HIGH_QUALITY + */ + public static final Key EDGE_MODE = + new Key("android.edge.mode", int.class); + + /** + *

    + * Select flash operation mode + *

    + * @see #FLASH_MODE_OFF + * @see #FLASH_MODE_SINGLE + * @see #FLASH_MODE_TORCH + */ + public static final Key FLASH_MODE = + new Key("android.flash.mode", int.class); + + /** + *

    + * GPS coordinates to include in output JPEG + * EXIF + *

    + */ + public static final Key JPEG_GPS_COORDINATES = + new Key("android.jpeg.gpsCoordinates", double[].class); + + /** + *

    + * 32 characters describing GPS algorithm to + * include in EXIF + *

    + */ + public static final Key JPEG_GPS_PROCESSING_METHOD = + new Key("android.jpeg.gpsProcessingMethod", String.class); + + /** + *

    + * Time GPS fix was made to include in + * EXIF + *

    + */ + public static final Key JPEG_GPS_TIMESTAMP = + new Key("android.jpeg.gpsTimestamp", long.class); + + /** + *

    + * Orientation of JPEG image to + * write + *

    + */ + public static final Key JPEG_ORIENTATION = + new Key("android.jpeg.orientation", int.class); + + /** + *

    + * Compression quality of the final JPEG + * image + *

    + *

    + * 85-95 is typical usage range + *

    + */ + public static final Key JPEG_QUALITY = + new Key("android.jpeg.quality", byte.class); + + /** + *

    + * Compression quality of JPEG + * thumbnail + *

    + */ + public static final Key JPEG_THUMBNAIL_QUALITY = + new Key("android.jpeg.thumbnailQuality", byte.class); + + /** + *

    + * Resolution of embedded JPEG + * thumbnail + *

    + */ + public static final Key JPEG_THUMBNAIL_SIZE = + new Key("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class); + + /** + *

    + * Size of the lens aperture + *

    + *

    + * Will not be supported on most devices. Can only + * pick from supported list + *

    + */ + public static final Key LENS_APERTURE = + new Key("android.lens.aperture", float.class); + + /** + *

    + * State of lens neutral density + * filter(s) + *

    + *

    + * Will not be supported on most devices. Can only + * pick from supported list + *

    + */ + public static final Key LENS_FILTER_DENSITY = + new Key("android.lens.filterDensity", float.class); + + /** + *

    + * Lens optical zoom setting + *

    + *

    + * Will not be supported on most devices. + *

    + */ + public static final Key LENS_FOCAL_LENGTH = + new Key("android.lens.focalLength", float.class); + + /** + *

    + * Distance to plane of sharpest focus, + * measured from frontmost surface of the lens + *

    + *

    + * 0 = infinity focus. Used value should be clamped + * to (0,minimum focus distance) + *

    + */ + public static final Key LENS_FOCUS_DISTANCE = + new Key("android.lens.focusDistance", float.class); + + /** + *

    + * Whether optical image stabilization is + * enabled. + *

    + *

    + * Will not be supported on most devices. + *

    + * @see #LENS_OPTICAL_STABILIZATION_MODE_OFF + * @see #LENS_OPTICAL_STABILIZATION_MODE_ON + */ + public static final Key LENS_OPTICAL_STABILIZATION_MODE = + new Key("android.lens.opticalStabilizationMode", int.class); + + /** + *

    + * Mode of operation for the noise reduction + * algorithm + *

    + * @see #NOISE_REDUCTION_MODE_OFF + * @see #NOISE_REDUCTION_MODE_FAST + * @see #NOISE_REDUCTION_MODE_HIGH_QUALITY + */ + public static final Key NOISE_REDUCTION_MODE = + new Key("android.noiseReduction.mode", int.class); + + /** + *

    + * An application-specified ID for the current + * request. Must be maintained unchanged in output + * frame + *

    + * + * @hide + */ + public static final Key REQUEST_ID = + new Key("android.request.id", int.class); + + /** + *

    + * (x, y, width, height). + *

    + * A rectangle with the top-level corner of (x,y) and size + * (width, height). The region of the sensor that is used for + * output. Each stream must use this rectangle to produce its + * output, cropping to a smaller region if necessary to + * maintain the stream's aspect ratio. + *

    + * HAL2.x uses only (x, y, width) + *

    + *

    + * Any additional per-stream cropping must be done to + * maximize the final pixel area of the stream. + *

    + * For example, if the crop region is set to a 4:3 aspect + * ratio, then 4:3 streams should use the exact crop + * region. 16:9 streams should further crop vertically + * (letterbox). + *

    + * Conversely, if the crop region is set to a 16:9, then 4:3 + * outputs should crop horizontally (pillarbox), and 16:9 + * streams should match exactly. These additional crops must + * be centered within the crop region. + *

    + * The output streams must maintain square pixels at all + * times, no matter what the relative aspect ratios of the + * crop region and the stream are. Negative values for + * corner are allowed for raw output if full pixel array is + * larger than active pixel array. Width and height may be + * rounded to nearest larger supportable width, especially + * for raw output, where only a few fixed scales may be + * possible. The width and height of the crop region cannot + * be set to be smaller than floor( activeArraySize.width / + * android.scaler.maxDigitalZoom ) and floor( + * activeArraySize.height / android.scaler.maxDigitalZoom), + * respectively. + *

    + */ + public static final Key SCALER_CROP_REGION = + new Key("android.scaler.cropRegion", android.graphics.Rect.class); + + /** + *

    + * Duration each pixel is exposed to + * light. + *

    + * If the sensor can't expose this exact duration, it should shorten the + * duration exposed to the nearest possible value (rather than expose longer). + *

    + *

    + * 1/10000 - 30 sec range. No bulb mode + *

    + */ + public static final Key SENSOR_EXPOSURE_TIME = + new Key("android.sensor.exposureTime", long.class); + + /** + *

    + * Duration from start of frame exposure to + * start of next frame exposure + *

    + *

    + * Exposure time has priority, so duration is set to + * max(duration, exposure time + overhead) + *

    + */ + public static final Key SENSOR_FRAME_DURATION = + new Key("android.sensor.frameDuration", long.class); + + /** + *

    + * Gain applied to image data. Must be + * implemented through analog gain only if set to values + * below 'maximum analog sensitivity'. + *

    + * If the sensor can't apply this exact gain, it should lessen the + * gain to the nearest possible value (rather than gain more). + *

    + *

    + * ISO 12232:2006 REI method + *

    + */ + public static final Key SENSOR_SENSITIVITY = + new Key("android.sensor.sensitivity", int.class); + + /** + *

    + * State of the face detector + * unit + *

    + *

    + * Whether face detection is enabled, and whether it + * should output just the basic fields or the full set of + * fields. Value must be one of the + * android.statistics.info.availableFaceDetectModes. + *

    + * @see #STATISTICS_FACE_DETECT_MODE_OFF + * @see #STATISTICS_FACE_DETECT_MODE_SIMPLE + * @see #STATISTICS_FACE_DETECT_MODE_FULL + */ + public static final Key STATISTICS_FACE_DETECT_MODE = + new Key("android.statistics.faceDetectMode", int.class); + + /** + *

    + * Whether the HAL needs to output the lens + * shading map in output result metadata + *

    + *

    + * When set to ON, + * android.statistics.lensShadingMap must be provided in + * the output result metdata. + *

    + * @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF + * @see #STATISTICS_LENS_SHADING_MAP_MODE_ON + */ + public static final Key STATISTICS_LENS_SHADING_MAP_MODE = + new Key("android.statistics.lensShadingMapMode", int.class); + + /** + *

    + * Table mapping blue input values to output + * values + *

    + *

    + * Tonemapping / contrast / gamma curve for the blue + * channel, to use when android.tonemap.mode is CONTRAST_CURVE. + *

    + * See android.tonemap.curveRed for more details. + *

    + */ + public static final Key TONEMAP_CURVE_BLUE = + new Key("android.tonemap.curveBlue", float[].class); + + /** + *

    + * Table mapping green input values to output + * values + *

    + *

    + * Tonemapping / contrast / gamma curve for the green + * channel, to use when android.tonemap.mode is CONTRAST_CURVE. + *

    + * See android.tonemap.curveRed for more details. + *

    + */ + public static final Key TONEMAP_CURVE_GREEN = + new Key("android.tonemap.curveGreen", float[].class); + + /** + *

    + * Table mapping red input values to output + * values + *

    + *

    + * Tonemapping / contrast / gamma curve for the red + * channel, to use when android.tonemap.mode is CONTRAST_CURVE. + *

    + * Since the input and output ranges may vary depending on + * the camera pipeline, the input and output pixel values + * are represented by normalized floating-point values + * between 0 and 1, with 0 == black and 1 == white. + *

    + * The curve should be linearly interpolated between the + * defined points. The points will be listed in increasing + * order of P_IN. For example, if the array is: [0.0, 0.0, + * 0.3, 0.5, 1.0, 1.0], then the input->output mapping + * for a few sample points would be: 0 -> 0, 0.15 -> + * 0.25, 0.3 -> 0.5, 0.5 -> 0.64 + *

    + */ + public static final Key TONEMAP_CURVE_RED = + new Key("android.tonemap.curveRed", float[].class); + + /** + * @see #TONEMAP_MODE_CONTRAST_CURVE + * @see #TONEMAP_MODE_FAST + * @see #TONEMAP_MODE_HIGH_QUALITY + */ + public static final Key TONEMAP_MODE = + new Key("android.tonemap.mode", int.class); + + /** + *

    + * This LED is nominally used to indicate to the user + * that the camera is powered on and may be streaming images back to the + * Application Processor. In certain rare circumstances, the OS may + * disable this when video is processed locally and not transmitted to + * any untrusted applications. + *

    + * In particular, the LED *must* always be on when the data could be + * transmitted off the device. The LED *should* always be on whenever + * data is stored locally on the device. + *

    + * The LED *may* be off if a trusted application is using the data that + * doesn't violate the above rules. + *

    + * + * @hide + */ + public static final Key LED_TRANSMIT = + new Key("android.led.transmit", boolean.class); + + /** + *

    + * Whether black-level compensation is locked + * to its current values, or is free to vary + *

    + *

    + * When set to ON, the values used for black-level + * compensation must not change until the lock is set to + * OFF + *

    + * Since changes to certain capture parameters (such as + * exposure time) may require resetting of black level + * compensation, the HAL must report whether setting the + * black level lock was successful in the output result + * metadata. + *

    + * The black level locking must happen at the sensor, and not at the ISP. + * If for some reason black level locking is no longer legal (for example, + * the analog gain has changed, which forces black levels to be + * recalculated), then the HAL is free to override this request (and it + * must report 'OFF' when this does happen) until the next time locking + * is legal again. + *

    + */ + public static final Key BLACK_LEVEL_LOCK = + new Key("android.blackLevel.lock", boolean.class); + + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * End generated code + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ +} diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java new file mode 100644 index 0000000000000000000000000000000000000000..dbd0457e525f5c372b65508a6de47020531e4d80 --- /dev/null +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -0,0 +1,1013 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2; + +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.camera2.impl.CameraMetadataNative; + +/** + *

    The results of a single image capture from the image sensor.

    + * + *

    Contains the final configuration for the capture hardware (sensor, lens, + * flash), the processing pipeline, the control algorithms, and the output + * buffers.

    + * + *

    CaptureResults are produced by a {@link CameraDevice} after processing a + * {@link CaptureRequest}. All properties listed for capture requests can also + * be queried on the capture result, to determine the final values used for + * capture. The result also includes additional metadata about the state of the + * camera device during the capture.

    + * + */ +public final class CaptureResult extends CameraMetadata { + + private final CameraMetadataNative mResults; + private final CaptureRequest mRequest; + private final int mSequenceId; + + /** + * Takes ownership of the passed-in properties object + * @hide + */ + public CaptureResult(CameraMetadataNative results, CaptureRequest parent, int sequenceId) { + if (results == null) { + throw new IllegalArgumentException("results was null"); + } + + if (parent == null) { + throw new IllegalArgumentException("parent was null"); + } + + mResults = results; + mRequest = parent; + mSequenceId = sequenceId; + } + + @Override + public T get(Key key) { + return mResults.get(key); + } + + /** + * Get the request associated with this result. + * + *

    Whenever a request is successfully captured, with + * {@link CameraDevice.CaptureListener#onCaptureCompleted}, + * the {@code result}'s {@code getRequest()} will return that {@code request}. + *

    + * + *

    In particular, + *

    cameraDevice.capture(someRequest, new CaptureListener() {
    +     *     {@literal @}Override
    +     *     void onCaptureCompleted(CaptureRequest myRequest, CaptureResult myResult) {
    +     *         assert(myResult.getRequest.equals(myRequest) == true);
    +     *     }
    +     * };
    +     * 
    + *

    + * + * @return The request associated with this result. Never {@code null}. + */ + public CaptureRequest getRequest() { + return mRequest; + } + + /** + * Get the frame number associated with this result. + * + *

    Whenever a request has been processed, regardless of failure or success, + * it gets a unique frame number assigned to its future result/failure.

    + * + *

    This value monotonically increments, starting with 0, + * for every new result or failure; and the scope is the lifetime of the + * {@link CameraDevice}.

    + * + * @return int frame number + */ + public int getFrameNumber() { + return get(REQUEST_FRAME_COUNT); + } + + /** + * The sequence ID for this failure that was returned by the + * {@link CameraDevice#capture} family of functions. + * + *

    The sequence ID is a unique monotonically increasing value starting from 0, + * incremented every time a new group of requests is submitted to the CameraDevice.

    + * + * @return int The ID for the sequence of requests that this capture result is a part of + * + * @see CameraDevice.CaptureListener#onCaptureSequenceCompleted + */ + public int getSequenceId() { + return mSequenceId; + } + + /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * The key entries below this point are generated from metadata + * definitions in /system/media/camera/docs. Do not modify by hand or + * modify the comment blocks at the start or end. + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ + + /** + *

    + * A color transform matrix to use to transform + * from sensor RGB color space to output linear sRGB color space + *

    + *

    + * This matrix is either set by HAL when the request + * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * directly by the application in the request when the + * android.colorCorrection.mode is TRANSFORM_MATRIX. + *

    + * In the latter case, the HAL may round the matrix to account + * for precision issues; the final rounded matrix should be + * reported back in this matrix result metadata. + *

    + */ + public static final Key COLOR_CORRECTION_TRANSFORM = + new Key("android.colorCorrection.transform", Rational[].class); + + /** + *

    + * Gains applying to Bayer color channels for + * white-balance + *

    + *

    + * The 4-channel white-balance gains are defined in + * the order of [R G_even G_odd B], where G_even is the gain + * for green pixels on even rows of the output, and G_odd + * is the gain for greenpixels on the odd rows. if a HAL + * does not support a separate gain for even/odd green channels, + * it should use the G_even value,and write G_odd equal to + * G_even in the output result metadata. + *

    + * This array is either set by HAL when the request + * android.colorCorrection.mode is not TRANSFORM_MATRIX, or + * directly by the application in the request when the + * android.colorCorrection.mode is TRANSFORM_MATRIX. + *

    + * The ouput should be the gains actually applied by the HAL to + * the current frame. + *

    + */ + public static final Key COLOR_CORRECTION_GAINS = + new Key("android.colorCorrection.gains", float[].class); + + /** + *

    + * The ID sent with the latest + * CAMERA2_TRIGGER_PRECAPTURE_METERING call + *

    + *

    + * Must be 0 if no + * CAMERA2_TRIGGER_PRECAPTURE_METERING trigger received yet + * by HAL. Always updated even if AE algorithm ignores the + * trigger + *

    + * + * @hide + */ + public static final Key CONTROL_AE_PRECAPTURE_ID = + new Key("android.control.aePrecaptureId", int.class); + + /** + *

    + * List of areas to use for + * metering + *

    + *

    + * Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined inclusive of the + * specified coordinates. + *

    + * The coordinate system is based on the active pixel array, + * with (0,0) being the top-left pixel in the active pixel array, and + * (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1) being the + * bottom-right pixel in the active pixel array. The weight + * should be nonnegative. + *

    + * If all regions have 0 weight, then no specific metering area + * needs to be used by the HAL. If the metering region is + * outside the current android.scaler.cropRegion, the HAL + * should ignore the sections outside the region and output the + * used sections in the frame metadata + *

    + */ + public static final Key CONTROL_AE_REGIONS = + new Key("android.control.aeRegions", int[].class); + + /** + *

    + * Current state of AE algorithm + *

    + *

    + * Whenever the AE algorithm state changes, a + * MSG_AUTOEXPOSURE notification must be send if a + * notification callback is registered. + *

    + * @see #CONTROL_AE_STATE_INACTIVE + * @see #CONTROL_AE_STATE_SEARCHING + * @see #CONTROL_AE_STATE_CONVERGED + * @see #CONTROL_AE_STATE_LOCKED + * @see #CONTROL_AE_STATE_FLASH_REQUIRED + * @see #CONTROL_AE_STATE_PRECAPTURE + */ + public static final Key CONTROL_AE_STATE = + new Key("android.control.aeState", int.class); + + /** + *

    + * Whether AF is currently enabled, and what + * mode it is set to + *

    + * @see #CONTROL_AF_MODE_OFF + * @see #CONTROL_AF_MODE_AUTO + * @see #CONTROL_AF_MODE_MACRO + * @see #CONTROL_AF_MODE_CONTINUOUS_VIDEO + * @see #CONTROL_AF_MODE_CONTINUOUS_PICTURE + * @see #CONTROL_AF_MODE_EDOF + */ + public static final Key CONTROL_AF_MODE = + new Key("android.control.afMode", int.class); + + /** + *

    + * List of areas to use for focus + * estimation + *

    + *

    + * Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined inclusive of the + * specified coordinates. + *

    + * The coordinate system is based on the active pixel array, + * with (0,0) being the top-left pixel in the active pixel array, and + * (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1) being the + * bottom-right pixel in the active pixel array. The weight + * should be nonnegative. + *

    + * If all regions have 0 weight, then no specific focus area + * needs to be used by the HAL. If the focusing region is + * outside the current android.scaler.cropRegion, the HAL + * should ignore the sections outside the region and output the + * used sections in the frame metadata + *

    + */ + public static final Key CONTROL_AF_REGIONS = + new Key("android.control.afRegions", int[].class); + + /** + *

    + * Current state of AF algorithm + *

    + *

    + * Whenever the AF algorithm state changes, a + * MSG_AUTOFOCUS notification must be send if a notification + * callback is registered. + *

    + * @see #CONTROL_AF_STATE_INACTIVE + * @see #CONTROL_AF_STATE_PASSIVE_SCAN + * @see #CONTROL_AF_STATE_PASSIVE_FOCUSED + * @see #CONTROL_AF_STATE_ACTIVE_SCAN + * @see #CONTROL_AF_STATE_FOCUSED_LOCKED + * @see #CONTROL_AF_STATE_NOT_FOCUSED_LOCKED + * @see #CONTROL_AF_STATE_PASSIVE_UNFOCUSED + */ + public static final Key CONTROL_AF_STATE = + new Key("android.control.afState", int.class); + + /** + *

    + * The ID sent with the latest + * CAMERA2_TRIGGER_AUTOFOCUS call + *

    + *

    + * Must be 0 if no CAMERA2_TRIGGER_AUTOFOCUS trigger + * received yet by HAL. Always updated even if AF algorithm + * ignores the trigger + *

    + * + * @hide + */ + public static final Key CONTROL_AF_TRIGGER_ID = + new Key("android.control.afTriggerId", int.class); + + /** + *

    + * Whether AWB is currently setting the color + * transform fields, and what its illumination target + * is + *

    + *

    + * [BC - AWB lock,AWB modes] + *

    + * @see #CONTROL_AWB_MODE_OFF + * @see #CONTROL_AWB_MODE_AUTO + * @see #CONTROL_AWB_MODE_INCANDESCENT + * @see #CONTROL_AWB_MODE_FLUORESCENT + * @see #CONTROL_AWB_MODE_WARM_FLUORESCENT + * @see #CONTROL_AWB_MODE_DAYLIGHT + * @see #CONTROL_AWB_MODE_CLOUDY_DAYLIGHT + * @see #CONTROL_AWB_MODE_TWILIGHT + * @see #CONTROL_AWB_MODE_SHADE + */ + public static final Key CONTROL_AWB_MODE = + new Key("android.control.awbMode", int.class); + + /** + *

    + * List of areas to use for illuminant + * estimation + *

    + *

    + * Only used in AUTO mode. + *

    + * Each area is a rectangle plus weight: xmin, ymin, + * xmax, ymax, weight. The rectangle is defined inclusive of the + * specified coordinates. + *

    + * The coordinate system is based on the active pixel array, + * with (0,0) being the top-left pixel in the active pixel array, and + * (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1) being the + * bottom-right pixel in the active pixel array. The weight + * should be nonnegative. + *

    + * If all regions have 0 weight, then no specific metering area + * needs to be used by the HAL. If the metering region is + * outside the current android.scaler.cropRegion, the HAL + * should ignore the sections outside the region and output the + * used sections in the frame metadata + *

    + */ + public static final Key CONTROL_AWB_REGIONS = + new Key("android.control.awbRegions", int[].class); + + /** + *

    + * Current state of AWB algorithm + *

    + *

    + * Whenever the AWB algorithm state changes, a + * MSG_AUTOWHITEBALANCE notification must be send if a + * notification callback is registered. + *

    + * @see #CONTROL_AWB_STATE_INACTIVE + * @see #CONTROL_AWB_STATE_SEARCHING + * @see #CONTROL_AWB_STATE_CONVERGED + * @see #CONTROL_AWB_STATE_LOCKED + */ + public static final Key CONTROL_AWB_STATE = + new Key("android.control.awbState", int.class); + + /** + *

    + * Overall mode of 3A control + * routines + *

    + * @see #CONTROL_MODE_OFF + * @see #CONTROL_MODE_AUTO + * @see #CONTROL_MODE_USE_SCENE_MODE + */ + public static final Key CONTROL_MODE = + new Key("android.control.mode", int.class); + + /** + *

    + * Operation mode for edge + * enhancement + *

    + * @see #EDGE_MODE_OFF + * @see #EDGE_MODE_FAST + * @see #EDGE_MODE_HIGH_QUALITY + */ + public static final Key EDGE_MODE = + new Key("android.edge.mode", int.class); + + /** + *

    + * Select flash operation mode + *

    + * @see #FLASH_MODE_OFF + * @see #FLASH_MODE_SINGLE + * @see #FLASH_MODE_TORCH + */ + public static final Key FLASH_MODE = + new Key("android.flash.mode", int.class); + + /** + *

    + * Current state of the flash + * unit + *

    + * @see #FLASH_STATE_UNAVAILABLE + * @see #FLASH_STATE_CHARGING + * @see #FLASH_STATE_READY + * @see #FLASH_STATE_FIRED + */ + public static final Key FLASH_STATE = + new Key("android.flash.state", int.class); + + /** + *

    + * GPS coordinates to include in output JPEG + * EXIF + *

    + */ + public static final Key JPEG_GPS_COORDINATES = + new Key("android.jpeg.gpsCoordinates", double[].class); + + /** + *

    + * 32 characters describing GPS algorithm to + * include in EXIF + *

    + */ + public static final Key JPEG_GPS_PROCESSING_METHOD = + new Key("android.jpeg.gpsProcessingMethod", String.class); + + /** + *

    + * Time GPS fix was made to include in + * EXIF + *

    + */ + public static final Key JPEG_GPS_TIMESTAMP = + new Key("android.jpeg.gpsTimestamp", long.class); + + /** + *

    + * Orientation of JPEG image to + * write + *

    + */ + public static final Key JPEG_ORIENTATION = + new Key("android.jpeg.orientation", int.class); + + /** + *

    + * Compression quality of the final JPEG + * image + *

    + *

    + * 85-95 is typical usage range + *

    + */ + public static final Key JPEG_QUALITY = + new Key("android.jpeg.quality", byte.class); + + /** + *

    + * Compression quality of JPEG + * thumbnail + *

    + */ + public static final Key JPEG_THUMBNAIL_QUALITY = + new Key("android.jpeg.thumbnailQuality", byte.class); + + /** + *

    + * Resolution of embedded JPEG + * thumbnail + *

    + */ + public static final Key JPEG_THUMBNAIL_SIZE = + new Key("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class); + + /** + *

    + * Size of the lens aperture + *

    + *

    + * Will not be supported on most devices. Can only + * pick from supported list + *

    + */ + public static final Key LENS_APERTURE = + new Key("android.lens.aperture", float.class); + + /** + *

    + * State of lens neutral density + * filter(s) + *

    + *

    + * Will not be supported on most devices. Can only + * pick from supported list + *

    + */ + public static final Key LENS_FILTER_DENSITY = + new Key("android.lens.filterDensity", float.class); + + /** + *

    + * Lens optical zoom setting + *

    + *

    + * Will not be supported on most devices. + *

    + */ + public static final Key LENS_FOCAL_LENGTH = + new Key("android.lens.focalLength", float.class); + + /** + *

    + * Distance to plane of sharpest focus, + * measured from frontmost surface of the lens + *

    + *

    + * Should be zero for fixed-focus cameras + *

    + */ + public static final Key LENS_FOCUS_DISTANCE = + new Key("android.lens.focusDistance", float.class); + + /** + *

    + * The range of scene distances that are in + * sharp focus (depth of field) + *

    + *

    + * If variable focus not supported, can still report + * fixed depth of field range + *

    + */ + public static final Key LENS_FOCUS_RANGE = + new Key("android.lens.focusRange", float[].class); + + /** + *

    + * Whether optical image stabilization is + * enabled. + *

    + *

    + * Will not be supported on most devices. + *

    + * @see #LENS_OPTICAL_STABILIZATION_MODE_OFF + * @see #LENS_OPTICAL_STABILIZATION_MODE_ON + */ + public static final Key LENS_OPTICAL_STABILIZATION_MODE = + new Key("android.lens.opticalStabilizationMode", int.class); + + /** + *

    + * Current lens status + *

    + * @see #LENS_STATE_STATIONARY + * @see #LENS_STATE_MOVING + */ + public static final Key LENS_STATE = + new Key("android.lens.state", int.class); + + /** + *

    + * Mode of operation for the noise reduction + * algorithm + *

    + * @see #NOISE_REDUCTION_MODE_OFF + * @see #NOISE_REDUCTION_MODE_FAST + * @see #NOISE_REDUCTION_MODE_HIGH_QUALITY + */ + public static final Key NOISE_REDUCTION_MODE = + new Key("android.noiseReduction.mode", int.class); + + /** + *

    + * A frame counter set by the framework. This value monotonically + * increases with every new result (that is, each new result has a unique + * frameCount value). + *

    + *

    + * Reset on release() + *

    + */ + public static final Key REQUEST_FRAME_COUNT = + new Key("android.request.frameCount", int.class); + + /** + *

    + * An application-specified ID for the current + * request. Must be maintained unchanged in output + * frame + *

    + * + * @hide + */ + public static final Key REQUEST_ID = + new Key("android.request.id", int.class); + + /** + *

    + * (x, y, width, height). + *

    + * A rectangle with the top-level corner of (x,y) and size + * (width, height). The region of the sensor that is used for + * output. Each stream must use this rectangle to produce its + * output, cropping to a smaller region if necessary to + * maintain the stream's aspect ratio. + *

    + * HAL2.x uses only (x, y, width) + *

    + *

    + * Any additional per-stream cropping must be done to + * maximize the final pixel area of the stream. + *

    + * For example, if the crop region is set to a 4:3 aspect + * ratio, then 4:3 streams should use the exact crop + * region. 16:9 streams should further crop vertically + * (letterbox). + *

    + * Conversely, if the crop region is set to a 16:9, then 4:3 + * outputs should crop horizontally (pillarbox), and 16:9 + * streams should match exactly. These additional crops must + * be centered within the crop region. + *

    + * The output streams must maintain square pixels at all + * times, no matter what the relative aspect ratios of the + * crop region and the stream are. Negative values for + * corner are allowed for raw output if full pixel array is + * larger than active pixel array. Width and height may be + * rounded to nearest larger supportable width, especially + * for raw output, where only a few fixed scales may be + * possible. The width and height of the crop region cannot + * be set to be smaller than floor( activeArraySize.width / + * android.scaler.maxDigitalZoom ) and floor( + * activeArraySize.height / android.scaler.maxDigitalZoom), + * respectively. + *

    + */ + public static final Key SCALER_CROP_REGION = + new Key("android.scaler.cropRegion", android.graphics.Rect.class); + + /** + *

    + * Duration each pixel is exposed to + * light. + *

    + * If the sensor can't expose this exact duration, it should shorten the + * duration exposed to the nearest possible value (rather than expose longer). + *

    + *

    + * 1/10000 - 30 sec range. No bulb mode + *

    + */ + public static final Key SENSOR_EXPOSURE_TIME = + new Key("android.sensor.exposureTime", long.class); + + /** + *

    + * Duration from start of frame exposure to + * start of next frame exposure + *

    + *

    + * Exposure time has priority, so duration is set to + * max(duration, exposure time + overhead) + *

    + */ + public static final Key SENSOR_FRAME_DURATION = + new Key("android.sensor.frameDuration", long.class); + + /** + *

    + * Gain applied to image data. Must be + * implemented through analog gain only if set to values + * below 'maximum analog sensitivity'. + *

    + * If the sensor can't apply this exact gain, it should lessen the + * gain to the nearest possible value (rather than gain more). + *

    + *

    + * ISO 12232:2006 REI method + *

    + */ + public static final Key SENSOR_SENSITIVITY = + new Key("android.sensor.sensitivity", int.class); + + /** + *

    + * Time at start of exposure of first + * row + *

    + *

    + * Monotonic, should be synced to other timestamps in + * system + *

    + */ + public static final Key SENSOR_TIMESTAMP = + new Key("android.sensor.timestamp", long.class); + + /** + *

    + * The temperature of the sensor, sampled at the time + * exposure began for this frame. + *

    + * The thermal diode being queried should be inside the sensor PCB, or + * somewhere close to it. + *

    + * + * Optional - This value may be null on some devices. + * + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} - + * Present on all devices that report being FULL level hardware devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key. + */ + public static final Key SENSOR_TEMPERATURE = + new Key("android.sensor.temperature", float.class); + + /** + *

    + * State of the face detector + * unit + *

    + *

    + * Whether face detection is enabled, and whether it + * should output just the basic fields or the full set of + * fields. Value must be one of the + * android.statistics.info.availableFaceDetectModes. + *

    + * @see #STATISTICS_FACE_DETECT_MODE_OFF + * @see #STATISTICS_FACE_DETECT_MODE_SIMPLE + * @see #STATISTICS_FACE_DETECT_MODE_FULL + */ + public static final Key STATISTICS_FACE_DETECT_MODE = + new Key("android.statistics.faceDetectMode", int.class); + + /** + *

    + * List of unique IDs for detected + * faces + *

    + *

    + * Only available if faceDetectMode == FULL + *

    + */ + public static final Key STATISTICS_FACE_IDS = + new Key("android.statistics.faceIds", int[].class); + + /** + *

    + * List of landmarks for detected + * faces + *

    + *

    + * Only available if faceDetectMode == FULL + *

    + */ + public static final Key STATISTICS_FACE_LANDMARKS = + new Key("android.statistics.faceLandmarks", int[].class); + + /** + *

    + * List of the bounding rectangles for detected + * faces + *

    + *

    + * Only available if faceDetectMode != OFF + *

    + */ + public static final Key STATISTICS_FACE_RECTANGLES = + new Key("android.statistics.faceRectangles", android.graphics.Rect[].class); + + /** + *

    + * List of the face confidence scores for + * detected faces + *

    + *

    + * Only available if faceDetectMode != OFF. The value should be + * meaningful (for example, setting 100 at all times is illegal). + *

    + */ + public static final Key STATISTICS_FACE_SCORES = + new Key("android.statistics.faceScores", byte[].class); + + /** + *

    + * A low-resolution map of lens shading, per + * color channel + *

    + *

    + * Assume bilinear interpolation of map. The least + * shaded section of the image should have a gain factor + * of 1; all other sections should have gains above 1. + * the map should be on the order of 30-40 rows, and + * must be smaller than 64x64. + *

    + * When android.colorCorrection.mode = TRANSFORM_MATRIX, the map + * must take into account the colorCorrection settings. + *

    + */ + public static final Key STATISTICS_LENS_SHADING_MAP = + new Key("android.statistics.lensShadingMap", float[].class); + + /** + *

    + * The best-fit color channel gains calculated + * by the HAL's statistics units for the current output frame + *

    + *

    + * This may be different than the gains used for this frame, + * since statistics processing on data from a new frame + * typically completes after the transform has already been + * applied to that frame. + *

    + * The 4 channel gains are defined in Bayer domain, + * see android.colorCorrection.gains for details. + *

    + * This value should always be calculated by the AWB block, + * regardless of the android.control.* current values. + *

    + */ + public static final Key STATISTICS_PREDICTED_COLOR_GAINS = + new Key("android.statistics.predictedColorGains", float[].class); + + /** + *

    + * The best-fit color transform matrix estimate + * calculated by the HAL's statistics units for the current + * output frame + *

    + *

    + * The HAL must provide the estimate from its + * statistics unit on the white balance transforms to use + * for the next frame. These are the values the HAL believes + * are the best fit for the current output frame. This may + * be different than the transform used for this frame, since + * statistics processing on data from a new frame typically + * completes after the transform has already been applied to + * that frame. + *

    + * These estimates must be provided for all frames, even if + * capture settings and color transforms are set by the application. + *

    + * This value should always be calculated by the AWB block, + * regardless of the android.control.* current values. + *

    + */ + public static final Key STATISTICS_PREDICTED_COLOR_TRANSFORM = + new Key("android.statistics.predictedColorTransform", Rational[].class); + + /** + *

    + * The HAL estimated scene illumination lighting + * frequency + *

    + *

    + * Report NONE if there doesn't appear to be flickering + * illumination + *

    + * @see #STATISTICS_SCENE_FLICKER_NONE + * @see #STATISTICS_SCENE_FLICKER_50HZ + * @see #STATISTICS_SCENE_FLICKER_60HZ + */ + public static final Key STATISTICS_SCENE_FLICKER = + new Key("android.statistics.sceneFlicker", int.class); + + /** + *

    + * Table mapping blue input values to output + * values + *

    + *

    + * Tonemapping / contrast / gamma curve for the blue + * channel, to use when android.tonemap.mode is CONTRAST_CURVE. + *

    + * See android.tonemap.curveRed for more details. + *

    + */ + public static final Key TONEMAP_CURVE_BLUE = + new Key("android.tonemap.curveBlue", float[].class); + + /** + *

    + * Table mapping green input values to output + * values + *

    + *

    + * Tonemapping / contrast / gamma curve for the green + * channel, to use when android.tonemap.mode is CONTRAST_CURVE. + *

    + * See android.tonemap.curveRed for more details. + *

    + */ + public static final Key TONEMAP_CURVE_GREEN = + new Key("android.tonemap.curveGreen", float[].class); + + /** + *

    + * Table mapping red input values to output + * values + *

    + *

    + * Tonemapping / contrast / gamma curve for the red + * channel, to use when android.tonemap.mode is CONTRAST_CURVE. + *

    + * Since the input and output ranges may vary depending on + * the camera pipeline, the input and output pixel values + * are represented by normalized floating-point values + * between 0 and 1, with 0 == black and 1 == white. + *

    + * The curve should be linearly interpolated between the + * defined points. The points will be listed in increasing + * order of P_IN. For example, if the array is: [0.0, 0.0, + * 0.3, 0.5, 1.0, 1.0], then the input->output mapping + * for a few sample points would be: 0 -> 0, 0.15 -> + * 0.25, 0.3 -> 0.5, 0.5 -> 0.64 + *

    + */ + public static final Key TONEMAP_CURVE_RED = + new Key("android.tonemap.curveRed", float[].class); + + /** + * @see #TONEMAP_MODE_CONTRAST_CURVE + * @see #TONEMAP_MODE_FAST + * @see #TONEMAP_MODE_HIGH_QUALITY + */ + public static final Key TONEMAP_MODE = + new Key("android.tonemap.mode", int.class); + + /** + *

    + * This LED is nominally used to indicate to the user + * that the camera is powered on and may be streaming images back to the + * Application Processor. In certain rare circumstances, the OS may + * disable this when video is processed locally and not transmitted to + * any untrusted applications. + *

    + * In particular, the LED *must* always be on when the data could be + * transmitted off the device. The LED *should* always be on whenever + * data is stored locally on the device. + *

    + * The LED *may* be off if a trusted application is using the data that + * doesn't violate the above rules. + *

    + * + * @hide + */ + public static final Key LED_TRANSMIT = + new Key("android.led.transmit", boolean.class); + + /** + *

    + * Whether black-level compensation is locked + * to its current values, or is free to vary + *

    + *

    + * When set to ON, the values used for black-level + * compensation must not change until the lock is set to + * OFF + *

    + * Since changes to certain capture parameters (such as + * exposure time) may require resetting of black level + * compensation, the HAL must report whether setting the + * black level lock was successful in the output result + * metadata. + *

    + * The black level locking must happen at the sensor, and not at the ISP. + * If for some reason black level locking is no longer legal (for example, + * the analog gain has changed, which forces black levels to be + * recalculated), then the HAL is free to override this request (and it + * must report 'OFF' when this does happen) until the next time locking + * is legal again. + *

    + */ + public static final Key BLACK_LEVEL_LOCK = + new Key("android.blackLevel.lock", boolean.class); + + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ + * End generated code + *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ + + /** + *

    + * List of the {@link Face Faces} detected through camera face detection + * in this result. + *

    + *

    + * Only available if {@link #STATISTICS_FACE_DETECT_MODE} {@code !=} + * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_OFF OFF}. + *

    + * + * @see Face + */ + public static final Key STATISTICS_FACES = + new Key("android.statistics.faces", Face[].class); +} diff --git a/core/java/android/hardware/camera2/Face.java b/core/java/android/hardware/camera2/Face.java new file mode 100644 index 0000000000000000000000000000000000000000..ded8839dc15ce1a1ece5447c435aa361a811796a --- /dev/null +++ b/core/java/android/hardware/camera2/Face.java @@ -0,0 +1,262 @@ +/* + * 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.hardware.camera2; + +import android.graphics.Point; +import android.graphics.Rect; + +/** + * Describes a face detected in an image. + */ +public final class Face { + + /** + * The ID is {@code -1} when the optional set of fields is unsupported. + * + * @see Face#Face(Rect, int) + * @see #getId() + */ + public static final int ID_UNSUPPORTED = -1; + + /** + * The minimum possible value for the confidence level. + * + * @see #getScore() + */ + public static final int SCORE_MIN = 1; + + /** + * The maximum possible value for the confidence level. + * + * @see #getScore() + */ + public static final int SCORE_MAX = 100; + + private final Rect mBounds; + private final int mScore; + private final int mId; + private final Point mLeftEye; + private final Point mRightEye; + private final Point mMouth; + + /** + * Create a new face with all fields set. + * + *

    The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. + * They are only required when the {@link CaptureResult} reports that the value of key + * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is + * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}. + * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and + * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, + * rightEyePosition, and mouthPosition may be independently null or not-null.

    + * + * @param bounds Bounds of the face. + * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. + * @param id A unique ID per face visible to the tracker. + * @param leftEyePosition The position of the left eye. + * @param rightEyePosition The position of the right eye. + * @param mouthPosition The position of the mouth. + * + * @throws IllegalArgumentException + * if bounds is {@code null}, + * or if the confidence is not in the range of + * {@value #SCORE_MIN}-{@value #SCORE_MAX}, + * or if id is {@value #ID_UNSUPPORTED} and + * leftEyePosition/rightEyePosition/mouthPosition aren't all null, + * or else if id is negative. + * + * @hide + */ + public Face(Rect bounds, int score, int id, + Point leftEyePosition, Point rightEyePosition, Point mouthPosition) { + checkNotNull("bounds", bounds); + if (score < SCORE_MIN || score > SCORE_MAX) { + throw new IllegalArgumentException("Confidence out of range"); + } else if (id < 0 && id != ID_UNSUPPORTED) { + throw new IllegalArgumentException("Id out of range"); + } + if (id == ID_UNSUPPORTED) { + checkNull("leftEyePosition", leftEyePosition); + checkNull("rightEyePosition", rightEyePosition); + checkNull("mouthPosition", mouthPosition); + } + + mBounds = bounds; + mScore = score; + mId = id; + mLeftEye = leftEyePosition; + mRightEye = rightEyePosition; + mMouth = mouthPosition; + } + + /** + * Create a new face without the optional fields. + * + *

    The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. + * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and + * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, + * rightEyePosition, and mouthPosition may be independently null or not-null. When devices + * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as + * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, + * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition, + * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.

    + * + * @param bounds Bounds of the face. + * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. + * + * @throws IllegalArgumentException + * if bounds is {@code null}, + * or if the confidence is not in the range of + * {@value #SCORE_MIN}-{@value #SCORE_MAX}. + * + * @hide + */ + public Face(Rect bounds, int score) { + this(bounds, score, ID_UNSUPPORTED, + /*leftEyePosition*/null, /*rightEyePosition*/null, /*mouthPosition*/null); + } + + /** + * Bounds of the face. + * + *

    A rectangle relative to the sensor's + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0) + * representing the top-left corner of the active array rectangle.

    + * + *

    There is no constraints on the the Rectangle value other than it + * is not-{@code null}.

    + */ + public Rect getBounds() { + return mBounds; + } + + /** + * The confidence level for the detection of the face. + * + *

    The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}. + * {@value #SCORE_MAX} is the highest confidence.

    + * + *

    Depending on the device, even very low-confidence faces may be + * listed, so applications should filter out faces with low confidence, + * depending on the use case. For a typical point-and-shoot camera + * application that wishes to display rectangles around detected faces, + * filtering out faces with confidence less than half of {@value #SCORE_MAX} + * is recommended.

    + * + * @see #SCORE_MAX + * @see #SCORE_MIN + */ + public int getScore() { + return mScore; + } + + /** + * An unique id per face while the face is visible to the tracker. + * + *

    + * If the face leaves the field-of-view and comes back, it will get a new + * id.

    + * + *

    This is an optional field, may not be supported on all devices. + * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and + * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, + * rightEyePosition, and mouthPosition may be independently null or not-null. When devices + * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as + * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, + * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.

    + * + *

    This value will either be {@value #ID_UNSUPPORTED} or + * otherwise greater than {@code 0}.

    + * + * @see #ID_UNSUPPORTED + */ + public int getId() { + return mId; + } + + /** + * The coordinates of the center of the left eye. + * + *

    The coordinates are in + * the same space as the ones for {@link #getBounds}. This is an + * optional field, may not be supported on all devices. If not + * supported, the value will always be set to null. + * This value will always be null only if {@link #getId()} returns + * {@value #ID_UNSUPPORTED}.

    + * + * @return The left eye position, or {@code null} if unknown. + */ + public Point getLeftEyePosition() { + return mLeftEye; + } + + /** + * The coordinates of the center of the right eye. + * + *

    The coordinates are + * in the same space as the ones for {@link #getBounds}.This is an + * optional field, may not be supported on all devices. If not + * supported, the value will always be set to null. + * This value will always be null only if {@link #getId()} returns + * {@value #ID_UNSUPPORTED}.

    + * + * @return The right eye position, or {@code null} if unknown. + */ + public Point getRightEyePosition() { + return mRightEye; + } + + /** + * The coordinates of the center of the mouth. + * + *

    The coordinates are in + * the same space as the ones for {@link #getBounds}. This is an optional + * field, may not be supported on all devices. If not + * supported, the value will always be set to null. + * This value will always be null only if {@link #getId()} returns + * {@value #ID_UNSUPPORTED}.

    + *

    + * + * @return The mouth position, or {@code null} if unknown. + */ + public Point getMouthPosition() { + return mMouth; + } + + /** + * Represent the Face as a string for debugging purposes. + */ + @Override + public String toString() { + return String.format("{ bounds: %s, score: %s, id: %d, " + + "leftEyePosition: %s, rightEyePosition: %s, mouthPosition: %s }", + mBounds, mScore, mId, mLeftEye, mRightEye, mMouth); + } + + private static void checkNotNull(String name, Object obj) { + if (obj == null) { + throw new IllegalArgumentException(name + " was required, but it was null"); + } + } + + private static void checkNull(String name, Object obj) { + if (obj != null) { + throw new IllegalArgumentException(name + " was required to be null, but it wasn't"); + } + } +} diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl new file mode 100644 index 0000000000000000000000000000000000000000..02a73d66e33db946a87721ed9c103143e48839db --- /dev/null +++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2; + +import android.hardware.camera2.impl.CameraMetadataNative; + +/** @hide */ +interface ICameraDeviceCallbacks +{ + /** + * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h + */ + + oneway void onCameraError(int errorCode); + oneway void onCameraIdle(); + oneway void onCaptureStarted(int requestId, long timestamp); + oneway void onResultReceived(int requestId, in CameraMetadataNative result); +} diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl new file mode 100644 index 0000000000000000000000000000000000000000..193696363e231e77155bf3919371bf20a2000c9b --- /dev/null +++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl @@ -0,0 +1,50 @@ +/* + * 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.hardware.camera2; + +import android.view.Surface; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.CaptureRequest; + +/** @hide */ +interface ICameraDeviceUser +{ + /** + * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceUser.h + */ + void disconnect(); + + // ints here are status_t + + // non-negative value is the requestId. negative value is status_t + int submitRequest(in CaptureRequest request, boolean streaming); + + int cancelRequest(int requestId); + + int deleteStream(int streamId); + + // non-negative value is the stream ID. negative value is status_t + int createStream(int width, int height, int format, in Surface surface); + + int createDefaultRequest(int templateId, out CameraMetadataNative request); + + int getCameraInfo(out CameraMetadataNative info); + + int waitUntilIdle(); + + int flush(); +} diff --git a/core/java/android/hardware/camera2/Rational.java b/core/java/android/hardware/camera2/Rational.java new file mode 100644 index 0000000000000000000000000000000000000000..77b8c2685441d9920fecd290ba40bb7d66c7ffd3 --- /dev/null +++ b/core/java/android/hardware/camera2/Rational.java @@ -0,0 +1,201 @@ +/* + * 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.hardware.camera2; + +/** + * The rational data type used by CameraMetadata keys. Contains a pair of ints representing the + * numerator and denominator of a Rational number. This type is immutable. + */ +public final class Rational { + private final int mNumerator; + private final int mDenominator; + + /** + *

    Create a Rational with a given numerator and denominator.

    + * + *

    The signs of the numerator and the denominator may be flipped such that the denominator + * is always positive.

    + * + *

    A rational value with a 0-denominator may be constructed, but will have similar semantics + * as float NaN and INF values. The int getter functions return 0 in this case.

    + * + * @param numerator the numerator of the rational + * @param denominator the denominator of the rational + */ + public Rational(int numerator, int denominator) { + + if (denominator < 0) { + numerator = -numerator; + denominator = -denominator; + } + + mNumerator = numerator; + mDenominator = denominator; + } + + /** + * Gets the numerator of the rational. + */ + public int getNumerator() { + if (mDenominator == 0) { + return 0; + } + return mNumerator; + } + + /** + * Gets the denominator of the rational + */ + public int getDenominator() { + return mDenominator; + } + + private boolean isNaN() { + return mDenominator == 0 && mNumerator == 0; + } + + private boolean isInf() { + return mDenominator == 0 && mNumerator > 0; + } + + private boolean isNegInf() { + return mDenominator == 0 && mNumerator < 0; + } + + /** + *

    Compare this Rational to another object and see if they are equal.

    + * + *

    A Rational object can only be equal to another Rational object (comparing against any other + * type will return false).

    + * + *

    A Rational object is considered equal to another Rational object if and only if one of + * the following holds

    : + *
    • Both are NaN
    • + *
    • Both are infinities of the same sign
    • + *
    • Both have the same numerator and denominator in their reduced form
    • + *
    + * + *

    A reduced form of a Rational is calculated by dividing both the numerator and the + * denominator by their greatest common divisor.

    + * + *
    +     *      (new Rational(1, 2)).equals(new Rational(1, 2)) == true   // trivially true
    +     *      (new Rational(2, 3)).equals(new Rational(1, 2)) == false  // trivially false
    +     *      (new Rational(1, 2)).equals(new Rational(2, 4)) == true   // true after reduction
    +     *      (new Rational(0, 0)).equals(new Rational(0, 0)) == true   // NaN.equals(NaN)
    +     *      (new Rational(1, 0)).equals(new Rational(5, 0)) == true   // both are +infinity
    +     *      (new Rational(1, 0)).equals(new Rational(-1, 0)) == false // +infinity != -infinity
    +     * 
    + * + * @param obj a reference to another object + * + * @return A boolean that determines whether or not the two Rational objects are equal. + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (obj instanceof Rational) { + Rational other = (Rational) obj; + if (mDenominator == 0 || other.mDenominator == 0) { + if (isNaN() && other.isNaN()) { + return true; + } else if (isInf() && other.isInf() || isNegInf() && other.isNegInf()) { + return true; + } else { + return false; + } + } else if (mNumerator == other.mNumerator && mDenominator == other.mDenominator) { + return true; + } else { + int thisGcd = gcd(); + int otherGcd = other.gcd(); + + int thisNumerator = mNumerator / thisGcd; + int thisDenominator = mDenominator / thisGcd; + + int otherNumerator = other.mNumerator / otherGcd; + int otherDenominator = other.mDenominator / otherGcd; + + return (thisNumerator == otherNumerator && thisDenominator == otherDenominator); + } + } + return false; + } + + @Override + public String toString() { + if (isNaN()) { + return "NaN"; + } else if (isInf()) { + return "Infinity"; + } else if (isNegInf()) { + return "-Infinity"; + } else { + return mNumerator + "/" + mDenominator; + } + } + + /** + *

    Convert to a floating point representation.

    + * + * @return The floating point representation of this rational number. + * @hide + */ + public float toFloat() { + return (float) mNumerator / (float) mDenominator; + } + + @Override + public int hashCode() { + final long INT_MASK = 0xffffffffL; + + long asLong = INT_MASK & mNumerator; + asLong <<= 32; + + asLong |= (INT_MASK & mDenominator); + + return ((Long)asLong).hashCode(); + } + + /** + * Calculates the greatest common divisor using Euclid's algorithm. + * + * @return An int value representing the gcd. Always positive. + * @hide + */ + public int gcd() { + /** + * Non-recursive implementation of Euclid's algorithm: + * + * gcd(a, 0) := a + * gcd(a, b) := gcd(b, a mod b) + * + */ + + int a = mNumerator; + int b = mDenominator; + + while (b != 0) { + int oldB = b; + + b = a % b; + a = oldB; + } + + return Math.abs(a); + } +} diff --git a/core/java/android/hardware/camera2/Size.java b/core/java/android/hardware/camera2/Size.java new file mode 100644 index 0000000000000000000000000000000000000000..45aaeaef45a2c7212e4e0b84a0e9014594dd6d07 --- /dev/null +++ b/core/java/android/hardware/camera2/Size.java @@ -0,0 +1,77 @@ +/* + * 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.hardware.camera2; + +/** + * A simple immutable class for describing the dimensions of camera image + * buffers. + */ +public final class Size { + /** + * Create a new immutable Size instance + * + * @param width The width to store in the Size instance + * @param height The height to store in the Size instance + */ + public Size(int width, int height) { + mWidth = width; + mHeight = height; + } + + public final int getWidth() { + return mWidth; + } + + public final int getHeight() { + return mHeight; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof Size) { + Size other = (Size) obj; + return mWidth == other.mWidth && mHeight == other.mHeight; + } + return false; + } + + @Override + public String toString() { + return mWidth + "x" + mHeight; + } + + @Override + public int hashCode() { + final long INT_MASK = 0xffffffffL; + + long asLong = INT_MASK & mWidth; + asLong <<= 32; + + asLong |= (INT_MASK & mHeight); + + return ((Long)asLong).hashCode(); + } + + private final int mWidth; + private final int mHeight; +}; diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java new file mode 100644 index 0000000000000000000000000000000000000000..c5d0999213068cbe0439cda4b6915e4ae4b419c0 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -0,0 +1,637 @@ +/* + * 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.hardware.camera2.impl; + +import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +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.os.Handler; +import android.os.Looper; +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; + +/** + * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate + */ +public class CameraDevice implements android.hardware.camera2.CameraDevice { + + private final String TAG; + private final boolean DEBUG; + + // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) + private ICameraDeviceUser mRemoteDevice; + + private final Object mLock = new Object(); + private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); + + private final StateListener mDeviceListener; + private final Handler mDeviceHandler; + + private boolean mIdle = true; + + private final SparseArray mCaptureListenerMap = + new SparseArray(); + + private final Stack mRepeatingRequestIdStack = new Stack(); + // Map stream IDs to Surfaces + private final SparseArray mConfiguredOutputs = new SparseArray(); + + private final String mCameraId; + + // Runnables for all state transitions, except error, which needs the + // error code argument + + private final Runnable mCallOnOpened = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onOpened(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnUnconfigured = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onUnconfigured(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnActive = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onActive(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnBusy = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onBusy(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnClosed = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onClosed(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnIdle = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onIdle(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnDisconnected = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onDisconnected(CameraDevice.this); + } + } + }; + + public CameraDevice(String cameraId, StateListener listener, Handler handler) { + if (cameraId == null || listener == null || handler == null) { + throw new IllegalArgumentException("Null argument given"); + } + mCameraId = cameraId; + mDeviceListener = listener; + mDeviceHandler = handler; + TAG = String.format("CameraDevice-%s-JV", mCameraId); + DEBUG = Log.isLoggable(TAG, Log.DEBUG); + } + + public CameraDeviceCallbacks getCallbacks() { + return mCallbacks; + } + + public void setRemoteDevice(ICameraDeviceUser remoteDevice) { + // TODO: Move from decorator to direct binder-mediated exceptions + synchronized(mLock) { + mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); + + mDeviceHandler.post(mCallOnOpened); + mDeviceHandler.post(mCallOnUnconfigured); + } + } + + @Override + public String getId() { + return mCameraId; + } + + @Override + public void configureOutputs(List outputs) throws CameraAccessException { + // Treat a null input the same an empty list + if (outputs == null) { + outputs = new ArrayList(); + } + synchronized (mLock) { + checkIfCameraClosed(); + + HashSet addSet = new HashSet(outputs); // Streams to create + List deleteList = new ArrayList(); // 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 + } + } + + mDeviceHandler.post(mCallOnBusy); + stopRepeating(); + + try { + mRemoteDevice.waitUntilIdle(); + + // 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. + 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 wait until the previous operation completes."); + } + + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return; + } + + if (outputs.size() > 0) { + mDeviceHandler.post(mCallOnIdle); + } else { + mDeviceHandler.post(mCallOnUnconfigured); + } + } + } + + @Override + public CaptureRequest.Builder createCaptureRequest(int templateType) + throws CameraAccessException { + synchronized (mLock) { + checkIfCameraClosed(); + + CameraMetadataNative templatedRequest = new CameraMetadataNative(); + + try { + mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest); + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return null; + } + + CaptureRequest.Builder builder = + new CaptureRequest.Builder(templatedRequest); + + return builder; + } + } + + @Override + public int capture(CaptureRequest request, CaptureListener listener, Handler handler) + throws CameraAccessException { + return submitCaptureRequest(request, listener, handler, /*streaming*/false); + } + + @Override + public int captureBurst(List requests, CaptureListener listener, + Handler handler) throws CameraAccessException { + if (requests.isEmpty()) { + Log.w(TAG, "Capture burst request list is empty, do nothing!"); + return -1; + } + // TODO + throw new UnsupportedOperationException("Burst capture implemented yet"); + + } + + private int submitCaptureRequest(CaptureRequest request, CaptureListener listener, + Handler handler, boolean repeating) throws CameraAccessException { + + // Need a valid handler, or current thread needs to have a looper, if + // listener is valid + if (listener != null) { + handler = checkHandler(handler); + } + + synchronized (mLock) { + checkIfCameraClosed(); + int requestId; + + try { + requestId = mRemoteDevice.submitRequest(request, repeating); + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return -1; + } + if (listener != null) { + mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request, + handler, repeating)); + } + + if (repeating) { + mRepeatingRequestIdStack.add(requestId); + } + + if (mIdle) { + mDeviceHandler.post(mCallOnActive); + } + mIdle = false; + + return requestId; + } + } + + @Override + public int setRepeatingRequest(CaptureRequest request, CaptureListener listener, + Handler handler) throws CameraAccessException { + return submitCaptureRequest(request, listener, handler, /*streaming*/true); + } + + @Override + public int setRepeatingBurst(List requests, CaptureListener listener, + Handler handler) throws CameraAccessException { + if (requests.isEmpty()) { + Log.w(TAG, "Set Repeating burst request list is empty, do nothing!"); + return -1; + } + // TODO + throw new UnsupportedOperationException("Burst capture implemented yet"); + } + + @Override + public void stopRepeating() throws CameraAccessException { + + synchronized (mLock) { + checkIfCameraClosed(); + while (!mRepeatingRequestIdStack.isEmpty()) { + int requestId = mRepeatingRequestIdStack.pop(); + + try { + mRemoteDevice.cancelRequest(requestId); + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return; + } + } + } + } + + @Override + public void waitUntilIdle() throws CameraAccessException { + + synchronized (mLock) { + checkIfCameraClosed(); + if (!mRepeatingRequestIdStack.isEmpty()) { + throw new IllegalStateException("Active repeating request ongoing"); + } + + try { + mRemoteDevice.waitUntilIdle(); + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return; + } + } + } + + @Override + public void flush() throws CameraAccessException { + synchronized (mLock) { + checkIfCameraClosed(); + + mDeviceHandler.post(mCallOnBusy); + try { + mRemoteDevice.flush(); + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return; + } + } + } + + @Override + public void close() { + synchronized (mLock) { + + try { + if (mRemoteDevice != null) { + mRemoteDevice.disconnect(); + } + } catch (CameraRuntimeException e) { + Log.e(TAG, "Exception while closing: ", e.asChecked()); + } catch (RemoteException e) { + // impossible + } + + if (mRemoteDevice != null) { + mDeviceHandler.post(mCallOnClosed); + } + + mRemoteDevice = null; + } + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } + finally { + super.finalize(); + } + } + + static class CaptureListenerHolder { + + private final boolean mRepeating; + private final CaptureListener mListener; + private final CaptureRequest mRequest; + private final Handler mHandler; + + CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler, + boolean repeating) { + if (listener == null || handler == null) { + throw new UnsupportedOperationException( + "Must have a valid handler and a valid listener"); + } + mRepeating = repeating; + mHandler = handler; + mRequest = request; + mListener = listener; + } + + public boolean isRepeating() { + return mRepeating; + } + + public CaptureListener getListener() { + return mListener; + } + + public CaptureRequest getRequest() { + return mRequest; + } + + public Handler getHandler() { + return mHandler; + } + + } + + public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { + + // + // Constants below need to be kept up-to-date with + // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h + // + + // + // Error codes for onCameraError + // + + /** + * Camera has been disconnected + */ + static final int ERROR_CAMERA_DISCONNECTED = 0; + + /** + * Camera has encountered a device-level error + * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE + */ + static final int ERROR_CAMERA_DEVICE = 1; + + /** + * Camera has encountered a service-level error + * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE + */ + static final int ERROR_CAMERA_SERVICE = 2; + + @Override + public IBinder asBinder() { + return this; + } + + @Override + public void onCameraError(final int errorCode) { + Runnable r = null; + if (isClosed()) return; + + synchronized(mLock) { + switch (errorCode) { + case ERROR_CAMERA_DISCONNECTED: + r = mCallOnDisconnected; + break; + default: + Log.e(TAG, "Unknown error from camera device: " + errorCode); + // no break + case ERROR_CAMERA_DEVICE: + case ERROR_CAMERA_SERVICE: + r = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onError(CameraDevice.this, errorCode); + } + } + }; + break; + } + CameraDevice.this.mDeviceHandler.post(r); + } + } + + @Override + public void onCameraIdle() { + if (isClosed()) return; + + if (DEBUG) { + Log.d(TAG, "Camera now idle"); + } + synchronized (mLock) { + if (!CameraDevice.this.mIdle) { + CameraDevice.this.mDeviceHandler.post(mCallOnIdle); + } + CameraDevice.this.mIdle = true; + } + } + + @Override + public void onCaptureStarted(int requestId, final long timestamp) { + if (DEBUG) { + Log.d(TAG, "Capture started for id " + requestId); + } + final CaptureListenerHolder holder; + + // Get the listener for this frame ID, if there is one + synchronized (mLock) { + holder = CameraDevice.this.mCaptureListenerMap.get(requestId); + } + + if (holder == null) { + return; + } + + if (isClosed()) return; + + // Dispatch capture start notice + holder.getHandler().post( + new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + holder.getListener().onCaptureStarted( + CameraDevice.this, + holder.getRequest(), + timestamp); + } + } + }); + } + + @Override + public void onResultReceived(int requestId, CameraMetadataNative result) + throws RemoteException { + if (DEBUG) { + Log.d(TAG, "Received result for id " + requestId); + } + final CaptureListenerHolder holder; + + synchronized (mLock) { + // TODO: move this whole map into this class to make it more testable, + // exposing the methods necessary like subscribeToRequest, unsubscribe.. + // TODO: make class static class + + holder = CameraDevice.this.mCaptureListenerMap.get(requestId); + + // Clean up listener once we no longer expect to see it. + + // TODO: how to handle repeating listeners? + // we probably want cancelRequest to return # of times it already enqueued and + // keep a counter. + if (holder != null && !holder.isRepeating()) { + CameraDevice.this.mCaptureListenerMap.remove(requestId); + } + } + + // Check if we have a listener for this + if (holder == null) { + return; + } + + if (isClosed()) return; + + final CaptureRequest request = holder.getRequest(); + final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); + + holder.getHandler().post( + new Runnable() { + @Override + public void run() { + if (!CameraDevice.this.isClosed()){ + holder.getListener().onCaptureCompleted( + CameraDevice.this, + request, + resultAsCapture); + } + } + }); + } + + } + + /** + * Default handler management. If handler is null, get the current thread's + * Looper to create a Handler with. If no looper exists, throw exception. + */ + private Handler checkHandler(Handler handler) { + if (handler == null) { + Looper looper = Looper.myLooper(); + if (looper == null) { + throw new IllegalArgumentException( + "No handler given, and current thread has no looper!"); + } + handler = new Handler(looper); + } + return handler; + } + + private void checkIfCameraClosed() { + if (mRemoteDevice == null) { + throw new IllegalStateException("CameraDevice was already closed"); + } + } + + private boolean isClosed() { + synchronized(mLock) { + return (mRemoteDevice == null); + } + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl b/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4a89129b0c9bd13adc1eec1399b908e4fdf4c970 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl @@ -0,0 +1,20 @@ +/* + * 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.hardware.camera2.impl; + +/** @hide */ +parcelable CameraMetadataNative; diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java new file mode 100644 index 0000000000000000000000000000000000000000..072c5bb229d386e1e56376038ec87040aef17e80 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -0,0 +1,855 @@ +/* + * 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.hardware.camera2.impl; + +import android.graphics.ImageFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.Face; +import android.hardware.camera2.Rational; +import android.os.Parcelable; +import android.os.Parcel; +import android.util.Log; + +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Implementation of camera metadata marshal/unmarshal across Binder to + * the camera service + */ +public class CameraMetadataNative extends CameraMetadata implements Parcelable { + + private static final String TAG = "CameraMetadataJV"; + private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); + // this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h + private static final int NATIVE_JPEG_FORMAT = 0x21; + + public CameraMetadataNative() { + super(); + mMetadataPtr = nativeAllocate(); + if (mMetadataPtr == 0) { + throw new OutOfMemoryError("Failed to allocate native CameraMetadata"); + } + } + + /** + * Copy constructor - clone metadata + */ + public CameraMetadataNative(CameraMetadataNative other) { + super(); + mMetadataPtr = nativeAllocateCopy(other); + if (mMetadataPtr == 0) { + throw new OutOfMemoryError("Failed to allocate native CameraMetadata"); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public CameraMetadataNative createFromParcel(Parcel in) { + CameraMetadataNative metadata = new CameraMetadataNative(); + metadata.readFromParcel(in); + return metadata; + } + + @Override + public CameraMetadataNative[] newArray(int size) { + return new CameraMetadataNative[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + nativeWriteToParcel(dest); + } + + @SuppressWarnings("unchecked") + @Override + public T get(Key key) { + T value = getOverride(key); + if (value != null) { + return value; + } + + return getBase(key); + } + + public void readFromParcel(Parcel in) { + nativeReadFromParcel(in); + } + + /** + * Set a camera metadata field to a value. The field definitions can be + * found in {@link CameraCharacteristics}, {@link CaptureResult}, and + * {@link CaptureRequest}. + * + * @param key The metadata field to write. + * @param value The value to set the field to, which must be of a matching + * type to the key. + */ + public void set(Key key, T value) { + if (setOverride(key, value)) { + return; + } + + setBase(key, value); + } + + // Keep up-to-date with camera_metadata.h + /** + * @hide + */ + public static final int TYPE_BYTE = 0; + /** + * @hide + */ + public static final int TYPE_INT32 = 1; + /** + * @hide + */ + public static final int TYPE_FLOAT = 2; + /** + * @hide + */ + public static final int TYPE_INT64 = 3; + /** + * @hide + */ + public static final int TYPE_DOUBLE = 4; + /** + * @hide + */ + public static final int TYPE_RATIONAL = 5; + /** + * @hide + */ + public static final int NUM_TYPES = 6; + + private void close() { + // this sets mMetadataPtr to 0 + nativeClose(); + mMetadataPtr = 0; // set it to 0 again to prevent eclipse from making this field final + } + + private static int getTypeSize(int nativeType) { + switch(nativeType) { + case TYPE_BYTE: + return 1; + case TYPE_INT32: + case TYPE_FLOAT: + return 4; + case TYPE_INT64: + case TYPE_DOUBLE: + case TYPE_RATIONAL: + return 8; + } + + throw new UnsupportedOperationException("Unknown type, can't get size " + + nativeType); + } + + private static Class getExpectedType(int nativeType) { + switch(nativeType) { + case TYPE_BYTE: + return Byte.TYPE; + case TYPE_INT32: + return Integer.TYPE; + case TYPE_FLOAT: + return Float.TYPE; + case TYPE_INT64: + return Long.TYPE; + case TYPE_DOUBLE: + return Double.TYPE; + case TYPE_RATIONAL: + return Rational.class; + } + + throw new UnsupportedOperationException("Unknown type, can't map to Java type " + + nativeType); + } + + @SuppressWarnings("unchecked") + private static int packSingleNative(T value, ByteBuffer buffer, Class type, + int nativeType, boolean sizeOnly) { + + if (!sizeOnly) { + /** + * Rewrite types when the native type doesn't match the managed type + * - Boolean -> Byte + * - Integer -> Byte + */ + + if (nativeType == TYPE_BYTE && type == Boolean.TYPE) { + // Since a boolean can't be cast to byte, and we don't want to use putBoolean + boolean asBool = (Boolean) value; + byte asByte = (byte) (asBool ? 1 : 0); + value = (T) (Byte) asByte; + } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) { + int asInt = (Integer) value; + byte asByte = (byte) asInt; + value = (T) (Byte) asByte; + } else if (type != getExpectedType(nativeType)) { + throw new UnsupportedOperationException("Tried to pack a type of " + type + + " but we expected the type to be " + getExpectedType(nativeType)); + } + + if (nativeType == TYPE_BYTE) { + buffer.put((Byte) value); + } else if (nativeType == TYPE_INT32) { + buffer.putInt((Integer) value); + } else if (nativeType == TYPE_FLOAT) { + buffer.putFloat((Float) value); + } else if (nativeType == TYPE_INT64) { + buffer.putLong((Long) value); + } else if (nativeType == TYPE_DOUBLE) { + buffer.putDouble((Double) value); + } else if (nativeType == TYPE_RATIONAL) { + Rational r = (Rational) value; + buffer.putInt(r.getNumerator()); + buffer.putInt(r.getDenominator()); + } + + } + + return getTypeSize(nativeType); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static int packSingle(T value, ByteBuffer buffer, Class type, int nativeType, + boolean sizeOnly) { + + int size = 0; + + if (type.isPrimitive() || type == Rational.class) { + size = packSingleNative(value, buffer, type, nativeType, sizeOnly); + } else if (type.isEnum()) { + size = packEnum((Enum)value, buffer, (Class)type, nativeType, sizeOnly); + } else if (type.isArray()) { + size = packArray(value, buffer, type, nativeType, sizeOnly); + } else { + size = packClass(value, buffer, type, nativeType, sizeOnly); + } + + return size; + } + + private static > int packEnum(T value, ByteBuffer buffer, Class type, + int nativeType, boolean sizeOnly) { + + // TODO: add support for enums with their own values. + return packSingleNative(getEnumValue(value), buffer, Integer.TYPE, nativeType, sizeOnly); + } + + @SuppressWarnings("unchecked") + private static int packClass(T value, ByteBuffer buffer, Class type, int nativeType, + boolean sizeOnly) { + + MetadataMarshalClass marshaler = getMarshaler(type, nativeType); + if (marshaler == null) { + throw new IllegalArgumentException(String.format("Unknown Key type: %s", type)); + } + + return marshaler.marshal(value, buffer, nativeType, sizeOnly); + } + + private static int packArray(T value, ByteBuffer buffer, Class type, int nativeType, + boolean sizeOnly) { + + int size = 0; + int arrayLength = Array.getLength(value); + + @SuppressWarnings("unchecked") + Class componentType = (Class)type.getComponentType(); + + for (int i = 0; i < arrayLength; ++i) { + size += packSingle(Array.get(value, i), buffer, componentType, nativeType, sizeOnly); + } + + return size; + } + + @SuppressWarnings("unchecked") + private static T unpackSingleNative(ByteBuffer buffer, Class type, int nativeType) { + + T val; + + if (nativeType == TYPE_BYTE) { + val = (T) (Byte) buffer.get(); + } else if (nativeType == TYPE_INT32) { + val = (T) (Integer) buffer.getInt(); + } else if (nativeType == TYPE_FLOAT) { + val = (T) (Float) buffer.getFloat(); + } else if (nativeType == TYPE_INT64) { + val = (T) (Long) buffer.getLong(); + } else if (nativeType == TYPE_DOUBLE) { + val = (T) (Double) buffer.getDouble(); + } else if (nativeType == TYPE_RATIONAL) { + val = (T) new Rational(buffer.getInt(), buffer.getInt()); + } else { + throw new UnsupportedOperationException("Unknown type, can't unpack a native type " + + nativeType); + } + + /** + * Rewrite types when the native type doesn't match the managed type + * - Byte -> Boolean + * - Byte -> Integer + */ + + if (nativeType == TYPE_BYTE && type == Boolean.TYPE) { + // Since a boolean can't be cast to byte, and we don't want to use getBoolean + byte asByte = (Byte) val; + boolean asBool = asByte != 0; + val = (T) (Boolean) asBool; + } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) { + byte asByte = (Byte) val; + int asInt = asByte; + val = (T) (Integer) asInt; + } else if (type != getExpectedType(nativeType)) { + throw new UnsupportedOperationException("Tried to unpack a type of " + type + + " but we expected the type to be " + getExpectedType(nativeType)); + } + + return val; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static T unpackSingle(ByteBuffer buffer, Class type, int nativeType) { + + if (type.isPrimitive() || type == Rational.class) { + return unpackSingleNative(buffer, type, nativeType); + } + + if (type.isEnum()) { + return (T) unpackEnum(buffer, (Class)type, nativeType); + } + + if (type.isArray()) { + return unpackArray(buffer, type, nativeType); + } + + T instance = unpackClass(buffer, type, nativeType); + + return instance; + } + + private static > T unpackEnum(ByteBuffer buffer, Class type, + int nativeType) { + int ordinal = unpackSingleNative(buffer, Integer.TYPE, nativeType); + return getEnumFromValue(type, ordinal); + } + + private static T unpackClass(ByteBuffer buffer, Class type, int nativeType) { + + MetadataMarshalClass marshaler = getMarshaler(type, nativeType); + if (marshaler == null) { + throw new IllegalArgumentException("Unknown class type: " + type); + } + + return marshaler.unmarshal(buffer, nativeType); + } + + @SuppressWarnings("unchecked") + private static T unpackArray(ByteBuffer buffer, Class type, int nativeType) { + + Class componentType = type.getComponentType(); + Object array; + + int elementSize = getTypeSize(nativeType); + + MetadataMarshalClass marshaler = getMarshaler(componentType, nativeType); + if (marshaler != null) { + elementSize = marshaler.getNativeSize(nativeType); + } + + if (elementSize != MetadataMarshalClass.NATIVE_SIZE_DYNAMIC) { + int remaining = buffer.remaining(); + int arraySize = remaining / elementSize; + + if (VERBOSE) { + Log.v(TAG, + String.format( + "Attempting to unpack array (count = %d, element size = %d, bytes " + + "remaining = %d) for type %s", + arraySize, elementSize, remaining, type)); + } + + array = Array.newInstance(componentType, arraySize); + for (int i = 0; i < arraySize; ++i) { + Object elem = unpackSingle(buffer, componentType, nativeType); + Array.set(array, i, elem); + } + } else { + // Dynamic size, use an array list. + ArrayList arrayList = new ArrayList(); + + int primitiveSize = getTypeSize(nativeType); + while (buffer.remaining() >= primitiveSize) { + Object elem = unpackSingle(buffer, componentType, nativeType); + arrayList.add(elem); + } + + array = arrayList.toArray((T[]) Array.newInstance(componentType, 0)); + } + + if (buffer.remaining() != 0) { + Log.e(TAG, "Trailing bytes (" + buffer.remaining() + ") left over after unpacking " + + type); + } + + return (T) array; + } + + private T getBase(Key key) { + int tag = key.getTag(); + byte[] values = readValues(tag); + if (values == null) { + return null; + } + + int nativeType = getNativeType(tag); + + ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); + return unpackSingle(buffer, key.getType(), nativeType); + } + + // Need overwrite some metadata that has different definitions between native + // and managed sides. + @SuppressWarnings("unchecked") + private T getOverride(Key key) { + if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) { + return (T) getAvailableFormats(); + } else if (key.equals(CaptureResult.STATISTICS_FACES)) { + return (T) getFaces(); + } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) { + return (T) fixFaceRectangles(); + } + + // For other keys, get() falls back to getBase() + return null; + } + + private int[] getAvailableFormats() { + int[] availableFormats = getBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); + for (int i = 0; i < availableFormats.length; i++) { + // JPEG has different value between native and managed side, need override. + if (availableFormats[i] == NATIVE_JPEG_FORMAT) { + availableFormats[i] = ImageFormat.JPEG; + } + } + return availableFormats; + } + + private Face[] getFaces() { + final int FACE_LANDMARK_SIZE = 6; + + Integer faceDetectMode = get(CaptureResult.STATISTICS_FACE_DETECT_MODE); + if (faceDetectMode == null) { + Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE"); + faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE; + } else { + if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) { + return new Face[0]; + } + if (faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE && + faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) { + Log.w(TAG, "Unknown face detect mode: " + faceDetectMode); + return new Face[0]; + } + } + + // Face scores and rectangles are required by SIMPLE and FULL mode. + byte[] faceScores = get(CaptureResult.STATISTICS_FACE_SCORES); + Rect[] faceRectangles = get(CaptureResult.STATISTICS_FACE_RECTANGLES); + if (faceScores == null || faceRectangles == null) { + Log.w(TAG, "Expect face scores and rectangles to be non-null"); + return new Face[0]; + } else if (faceScores.length != faceRectangles.length) { + Log.w(TAG, String.format("Face score size(%d) doesn match face rectangle size(%d)!", + faceScores.length, faceRectangles.length)); + } + + // To be safe, make number of faces is the minimal of all face info metadata length. + int numFaces = Math.min(faceScores.length, faceRectangles.length); + // Face id and landmarks are only required by FULL mode. + int[] faceIds = get(CaptureResult.STATISTICS_FACE_IDS); + int[] faceLandmarks = get(CaptureResult.STATISTICS_FACE_LANDMARKS); + if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) { + if (faceIds == null || faceLandmarks == null) { + Log.w(TAG, "Expect face ids and landmarks to be non-null for FULL mode," + + "fallback to SIMPLE mode"); + faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE; + } else { + if (faceIds.length != numFaces || + faceLandmarks.length != numFaces * FACE_LANDMARK_SIZE) { + Log.w(TAG, String.format("Face id size(%d), or face landmark size(%d) don't" + + "match face number(%d)!", + faceIds.length, faceLandmarks.length * FACE_LANDMARK_SIZE, numFaces)); + } + // To be safe, make number of faces is the minimal of all face info metadata length. + numFaces = Math.min(numFaces, faceIds.length); + numFaces = Math.min(numFaces, faceLandmarks.length / FACE_LANDMARK_SIZE); + } + } + + ArrayList faceList = new ArrayList(); + if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE) { + for (int i = 0; i < numFaces; i++) { + if (faceScores[i] <= Face.SCORE_MAX && + faceScores[i] >= Face.SCORE_MIN) { + faceList.add(new Face(faceRectangles[i], faceScores[i])); + } + } + } else { + // CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL + for (int i = 0; i < numFaces; i++) { + if (faceScores[i] <= Face.SCORE_MAX && + faceScores[i] >= Face.SCORE_MIN && + faceIds[i] >= 0) { + Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]); + Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]); + Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]); + Face face = new Face(faceRectangles[i], faceScores[i], faceIds[i], + leftEye, rightEye, mouth); + faceList.add(face); + } + } + } + Face[] faces = new Face[faceList.size()]; + faceList.toArray(faces); + return faces; + } + + // Face rectangles are defined as (left, top, right, bottom) instead of + // (left, top, width, height) at the native level, so the normal Rect + // conversion that does (l, t, w, h) -> (l, t, r, b) is unnecessary. Undo + // that conversion here for just the faces. + private Rect[] fixFaceRectangles() { + Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES); + if (faceRectangles == null) return null; + + Rect[] fixedFaceRectangles = new Rect[faceRectangles.length]; + for (int i = 0; i < faceRectangles.length; i++) { + fixedFaceRectangles[i] = new Rect( + faceRectangles[i].left, + faceRectangles[i].top, + faceRectangles[i].right - faceRectangles[i].left, + faceRectangles[i].bottom - faceRectangles[i].top); + } + return fixedFaceRectangles; + } + + private void setBase(Key key, T value) { + int tag = key.getTag(); + + if (value == null) { + writeValues(tag, null); + return; + } + + int nativeType = getNativeType(tag); + + int size = packSingle(value, null, key.getType(), nativeType, /* sizeOnly */true); + + // TODO: Optimization. Cache the byte[] and reuse if the size is big enough. + byte[] values = new byte[size]; + + ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); + packSingle(value, buffer, key.getType(), nativeType, /*sizeOnly*/false); + + writeValues(tag, values); + } + + // Set the camera metadata override. + private boolean setOverride(Key key, T value) { + if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) { + return setAvailableFormats((int[]) value); + } + + // For other keys, set() falls back to setBase(). + return false; + } + + private boolean setAvailableFormats(int[] value) { + int[] availableFormat = value; + if (value == null) { + // Let setBase() to handle the null value case. + return false; + } + + int[] newValues = new int[availableFormat.length]; + for (int i = 0; i < availableFormat.length; i++) { + newValues[i] = availableFormat[i]; + if (availableFormat[i] == ImageFormat.JPEG) { + newValues[i] = NATIVE_JPEG_FORMAT; + } + } + + setBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, newValues); + return true; + } + + private long mMetadataPtr; // native CameraMetadata* + + private native long nativeAllocate(); + private native long nativeAllocateCopy(CameraMetadataNative other) + throws NullPointerException; + + private native synchronized void nativeWriteToParcel(Parcel dest); + private native synchronized void nativeReadFromParcel(Parcel source); + private native synchronized void nativeSwap(CameraMetadataNative other) + throws NullPointerException; + private native synchronized void nativeClose(); + private native synchronized boolean nativeIsEmpty(); + private native synchronized int nativeGetEntryCount(); + + private native synchronized byte[] nativeReadValues(int tag); + private native synchronized void nativeWriteValues(int tag, byte[] src); + + private static native int nativeGetTagFromKey(String keyName) + throws IllegalArgumentException; + private static native int nativeGetTypeFromTag(int tag) + throws IllegalArgumentException; + private static native void nativeClassInit(); + + /** + *

    Perform a 0-copy swap of the internal metadata with another object.

    + * + *

    Useful to convert a CameraMetadata into e.g. a CaptureRequest.

    + * + * @param other Metadata to swap with + * @throws NullPointerException if other was null + * @hide + */ + public void swap(CameraMetadataNative other) { + nativeSwap(other); + } + + /** + * @hide + */ + public int getEntryCount() { + return nativeGetEntryCount(); + } + + /** + * Does this metadata contain at least 1 entry? + * + * @hide + */ + public boolean isEmpty() { + return nativeIsEmpty(); + } + + /** + * Convert a key string into the equivalent native tag. + * + * @throws IllegalArgumentException if the key was not recognized + * @throws NullPointerException if the key was null + * + * @hide + */ + public static int getTag(String key) { + return nativeGetTagFromKey(key); + } + + /** + * Get the underlying native type for a tag. + * + * @param tag An integer tag, see e.g. {@link #getTag} + * @return An int enum for the metadata type, see e.g. {@link #TYPE_BYTE} + * + * @hide + */ + public static int getNativeType(int tag) { + return nativeGetTypeFromTag(tag); + } + + /** + *

    Updates the existing entry for tag with the new bytes pointed by src, erasing + * the entry if src was null.

    + * + *

    An empty array can be passed in to update the entry to 0 elements.

    + * + * @param tag An integer tag, see e.g. {@link #getTag} + * @param src An array of bytes, or null to erase the entry + * + * @hide + */ + public void writeValues(int tag, byte[] src) { + nativeWriteValues(tag, src); + } + + /** + *

    Returns a byte[] of data corresponding to this tag. Use a wrapped bytebuffer to unserialize + * the data properly.

    + * + *

    An empty array can be returned to denote an existing entry with 0 elements.

    + * + * @param tag An integer tag, see e.g. {@link #getTag} + * + * @return {@code null} if there were 0 entries for this tag, a byte[] otherwise. + * @hide + */ + public byte[] readValues(int tag) { + // TODO: Optimization. Native code returns a ByteBuffer instead. + return nativeReadValues(tag); + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + private static final HashMap, int[]> sEnumValues = + new HashMap, int[]>(); + /** + * Register a non-sequential set of values to be used with the pack/unpack functions. + * This enables get/set to correctly marshal the enum into a value that is C-compatible. + * + * @param enumType The class for an enum + * @param values A list of values mapping to the ordinals of the enum + * + * @hide + */ + public static > void registerEnumValues(Class enumType, int[] values) { + if (enumType.getEnumConstants().length != values.length) { + throw new IllegalArgumentException( + "Expected values array to be the same size as the enumTypes values " + + values.length + " for type " + enumType); + } + if (VERBOSE) { + Log.v(TAG, "Registered enum values for type " + enumType + " values"); + } + + sEnumValues.put(enumType, values); + } + + /** + * Get the numeric value from an enum. This is usually the same as the ordinal value for + * enums that have fully sequential values, although for C-style enums the range of values + * may not map 1:1. + * + * @param enumValue Enum instance + * @return Int guaranteed to be ABI-compatible with the C enum equivalent + */ + private static > int getEnumValue(T enumValue) { + int[] values; + values = sEnumValues.get(enumValue.getClass()); + + int ordinal = enumValue.ordinal(); + if (values != null) { + return values[ordinal]; + } + + return ordinal; + } + + /** + * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method. + * + * @param enumType Class of the enum we want to find + * @param value The numeric value of the enum + * @return An instance of the enum + */ + private static > T getEnumFromValue(Class enumType, int value) { + int ordinal; + + int[] registeredValues = sEnumValues.get(enumType); + if (registeredValues != null) { + ordinal = -1; + + for (int i = 0; i < registeredValues.length; ++i) { + if (registeredValues[i] == value) { + ordinal = i; + break; + } + } + } else { + ordinal = value; + } + + T[] values = enumType.getEnumConstants(); + + if (ordinal < 0 || ordinal >= values.length) { + throw new IllegalArgumentException( + String.format( + "Argument 'value' (%d) was not a valid enum value for type %s " + + "(registered? %b)", + value, + enumType, (registeredValues != null))); + } + + return values[ordinal]; + } + + static HashMap, MetadataMarshalClass> sMarshalerMap = new + HashMap, MetadataMarshalClass>(); + + private static void registerMarshaler(MetadataMarshalClass marshaler) { + sMarshalerMap.put(marshaler.getMarshalingClass(), marshaler); + } + + @SuppressWarnings("unchecked") + private static MetadataMarshalClass getMarshaler(Class type, int nativeType) { + MetadataMarshalClass marshaler = (MetadataMarshalClass) sMarshalerMap.get(type); + + if (marshaler != null && !marshaler.isNativeTypeSupported(nativeType)) { + throw new UnsupportedOperationException("Unsupported type " + nativeType + + " to be marshalled to/from a " + type); + } + + return marshaler; + } + + /** + * We use a class initializer to allow the native code to cache some field offsets + */ + static { + nativeClassInit(); + + if (VERBOSE) { + Log.v(TAG, "Shall register metadata marshalers"); + } + + // load built-in marshallers + registerMarshaler(new MetadataMarshalRect()); + registerMarshaler(new MetadataMarshalSize()); + registerMarshaler(new MetadataMarshalString()); + + if (VERBOSE) { + Log.v(TAG, "Registered metadata marshalers"); + } + } + +} diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java b/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java new file mode 100644 index 0000000000000000000000000000000000000000..6d224ef4f782c7bdae60287e3a206cc39fb03d79 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java @@ -0,0 +1,67 @@ +/* + * 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.hardware.camera2.impl; + +import java.nio.ByteBuffer; + +public interface MetadataMarshalClass { + + /** + * Marshal the specified object instance (value) into a byte buffer. + * + * @param value the value of type T that we wish to write into the byte buffer + * @param buffer the byte buffer into which the marshalled object will be written + * @param nativeType the native type, e.g. + * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}. + * Guaranteed to be one for which isNativeTypeSupported returns true. + * @param sizeOnly if this is true, don't write to the byte buffer. calculate the size only. + * @return the size that needs to be written to the byte buffer + */ + int marshal(T value, ByteBuffer buffer, int nativeType, boolean sizeOnly); + + /** + * Unmarshal a new object instance from the byte buffer. + * @param buffer the byte buffer, from which we will read the object + * @param nativeType the native type, e.g. + * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}. + * Guaranteed to be one for which isNativeTypeSupported returns true. + * @return a new instance of type T read from the byte buffer + */ + T unmarshal(ByteBuffer buffer, int nativeType); + + Class getMarshalingClass(); + + /** + * Determines whether or not this marshaller supports this native type. Most marshallers + * will are likely to only support one type. + * + * @param nativeType the native type, e.g. + * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE} + * @return true if it supports, false otherwise + */ + boolean isNativeTypeSupported(int nativeType); + + public static int NATIVE_SIZE_DYNAMIC = -1; + + /** + * How many bytes T will take up if marshalled to/from nativeType + * @param nativeType the native type, e.g. + * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE} + * @return a size in bytes, or NATIVE_SIZE_DYNAMIC if the size is dynamic + */ + int getNativeSize(int nativeType); +} diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java b/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java new file mode 100644 index 0000000000000000000000000000000000000000..ab72c4fea13d2d77eec32d1bcd3dc5322e936934 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java @@ -0,0 +1,67 @@ +/* + * 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.hardware.camera2.impl; + +import android.graphics.Rect; + +import java.nio.ByteBuffer; + +public class MetadataMarshalRect implements MetadataMarshalClass { + private static final int SIZE = 16; + + @Override + public int marshal(Rect value, ByteBuffer buffer, int nativeType, boolean sizeOnly) { + if (sizeOnly) { + return SIZE; + } + + buffer.putInt(value.left); + buffer.putInt(value.top); + buffer.putInt(value.width()); + buffer.putInt(value.height()); + + return SIZE; + } + + @Override + public Rect unmarshal(ByteBuffer buffer, int nativeType) { + + int left = buffer.getInt(); + int top = buffer.getInt(); + int width = buffer.getInt(); + int height = buffer.getInt(); + + int right = left + width; + int bottom = top + height; + + return new Rect(left, top, right, bottom); + } + + @Override + public Class getMarshalingClass() { + return Rect.class; + } + + @Override + public boolean isNativeTypeSupported(int nativeType) { + return nativeType == CameraMetadataNative.TYPE_INT32; + } + + @Override + public int getNativeSize(int nativeType) { + return SIZE; + } +} diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java b/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java new file mode 100644 index 0000000000000000000000000000000000000000..e8143e0e6e6851abd08e87ddb35b984b01252e1e --- /dev/null +++ b/core/java/android/hardware/camera2/impl/MetadataMarshalSize.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.hardware.camera2.impl; + +import android.hardware.camera2.Size; + +import java.nio.ByteBuffer; + +public class MetadataMarshalSize implements MetadataMarshalClass { + + private static final int SIZE = 8; + + @Override + public int marshal(Size value, ByteBuffer buffer, int nativeType, boolean sizeOnly) { + if (sizeOnly) { + return SIZE; + } + + buffer.putInt(value.getWidth()); + buffer.putInt(value.getHeight()); + + return SIZE; + } + + @Override + public Size unmarshal(ByteBuffer buffer, int nativeType) { + int width = buffer.getInt(); + int height = buffer.getInt(); + + return new Size(width, height); + } + + @Override + public Class getMarshalingClass() { + return Size.class; + } + + @Override + public boolean isNativeTypeSupported(int nativeType) { + return nativeType == CameraMetadataNative.TYPE_INT32; + } + + @Override + public int getNativeSize(int nativeType) { + return SIZE; + } +} diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalString.java b/core/java/android/hardware/camera2/impl/MetadataMarshalString.java new file mode 100644 index 0000000000000000000000000000000000000000..b61b8d3954598d30545f34ad7e11ad714e152dd9 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/MetadataMarshalString.java @@ -0,0 +1,79 @@ +/* + * 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.hardware.camera2.impl; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class MetadataMarshalString implements MetadataMarshalClass { + + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + + @Override + public int marshal(String value, ByteBuffer buffer, int nativeType, boolean sizeOnly) { + byte[] arr = value.getBytes(UTF8_CHARSET); + + if (!sizeOnly) { + buffer.put(arr); + buffer.put((byte)0); // metadata strings are NULL-terminated + } + + return arr.length + 1; + } + + @Override + public String unmarshal(ByteBuffer buffer, int nativeType) { + + buffer.mark(); // save the current position + + boolean foundNull = false; + int stringLength = 0; + while (buffer.hasRemaining()) { + if (buffer.get() == (byte)0) { + foundNull = true; + break; + } + + stringLength++; + } + if (!foundNull) { + throw new IllegalArgumentException("Strings must be null-terminated"); + } + + buffer.reset(); // go back to the previously marked position + + byte[] strBytes = new byte[stringLength + 1]; + buffer.get(strBytes, /*dstOffset*/0, stringLength + 1); // including null character + + // not including null character + return new String(strBytes, /*offset*/0, stringLength, UTF8_CHARSET); + } + + @Override + public Class getMarshalingClass() { + return String.class; + } + + @Override + public boolean isNativeTypeSupported(int nativeType) { + return nativeType == CameraMetadataNative.TYPE_BYTE; + } + + @Override + public int getNativeSize(int nativeType) { + return NATIVE_SIZE_DYNAMIC; + } +} diff --git a/core/java/android/hardware/camera2/impl/package.html b/core/java/android/hardware/camera2/impl/package.html new file mode 100644 index 0000000000000000000000000000000000000000..783d0a1b54d5275fc99f1e9673978c4fda3c8a9a --- /dev/null +++ b/core/java/android/hardware/camera2/impl/package.html @@ -0,0 +1,3 @@ + +{@hide} + diff --git a/core/java/android/hardware/camera2/package.html b/core/java/android/hardware/camera2/package.html new file mode 100644 index 0000000000000000000000000000000000000000..c61998403c8ee536b559f6ed73581426a091e48b --- /dev/null +++ b/core/java/android/hardware/camera2/package.html @@ -0,0 +1,86 @@ + + + +

    The android.hardware.camera2 package provides an interface to +individual camera devices connected to an Android device. It replaces +the deprecated {@link android.hardware.Camera} class.

    + +

    This package models a camera device as a pipeline, which takes in +input requests for capturing a single frame, captures the single image +per the request, and then outputs one capture result metadata packet, +plus a set of output image buffers for the request. The requests are +processed in-order, and multiple requests can be in flight at +once. Since the camera device is a pipeline with multiple stages, +having multiple requests in flight is required to maintain full +framerate on most Android devices.

    + +

    To enumerate, query, and open available camera devices, obtain a +{@link android.hardware.camera2.CameraManager} instance.

    + +

    Individual {@link android.hardware.camera2.CameraDevice +CameraDevices} provide a set of static property information that +describes the hardware device and the available settings and output +parameters for the device. This information is provided through the +{@link android.hardware.camera2.CameraCharacteristics} object.

    + +

    To capture or stream images from a camera device, the application +must first configure a set of output Surfaces for use with the camera +device, with {@link +android.hardware.camera2.CameraDevice#configureOutputs}. Each +Surface has to be pre-configured with an appropriate size and format +(if applicable) to match the sizes and formats available from the +camera device. A target Surface can be obtained from a variety of +classes, including {@link android.view.SurfaceView}, {@link +android.graphics.SurfaceTexture} via {@link +android.view.Surface#Surface(SurfaceTexture), {@link +android.media.MediaCodec}, and {@link android.media.ImageReader}. +

    + +

    The application then needs to construct a {@link +android.hardware.camera2.CaptureRequest}, which defines all the +capture parameters needed by a camera device to capture a single +image. The request also lists which of the configured output Surfaces +should be used as targets for this capture. The CameraDevice has a +{@link android.hardware.camera2.CameraDevice#createCaptureRequest +convenience factory method} for creating a request for a given use +case which is optimized for the Android device the application is +running on.

    + +

    Once the request has been set up, it can be handed to the +CameraDevice either for a one-shot {@link +android.hardware.camera2.CameraDevice#capture} or for an endlessly +{@link android.hardware.camera2.CameraDevice#setRepeatingRequest +repeating} use. Both methods also accept a list of requests to use as +a burst capture / repeating burst. Repeating requests have a lower +priority than captures, so a request submitted +through capture() while there's a repeating request +configured will be captured as soon as the current repeat (burst) +capture completes.

    + +

    After processing a request, the camera device will produce a {@link +android.hardware.camera2.CaptureResult} object, which contains +information about the state of the camera device at time of capture, +and the final settings used. These may vary somewhat from the request, +if rounding or resolving contradictory parameters was necessary. The +camera device will also send a frame of image data into each of the +output streams included in the request. These are produced +asynchronously relative to the output CaptureResult, sometimes +substantially later.

    + +{@hide} + + + diff --git a/core/java/android/hardware/camera2/utils/BinderHolder.aidl b/core/java/android/hardware/camera2/utils/BinderHolder.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f39d645a885726c4bcf7a62b58b97809f9538aff --- /dev/null +++ b/core/java/android/hardware/camera2/utils/BinderHolder.aidl @@ -0,0 +1,20 @@ +/* + * 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.hardware.camera2.utils; + +/** @hide */ +parcelable BinderHolder; diff --git a/core/java/android/hardware/camera2/utils/BinderHolder.java b/core/java/android/hardware/camera2/utils/BinderHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..9eea390427c68432757ac438165b52e747b8f39d --- /dev/null +++ b/core/java/android/hardware/camera2/utils/BinderHolder.java @@ -0,0 +1,74 @@ +/* + * 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.hardware.camera2.utils; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.IBinder; + +/** + * @hide + */ +public class BinderHolder implements Parcelable { + private IBinder mBinder = null; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mBinder); + } + + public void readFromParcel(Parcel src) { + mBinder = src.readStrongBinder(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public BinderHolder createFromParcel(Parcel in) { + return new BinderHolder(in); + } + + @Override + public BinderHolder[] newArray(int size) { + return new BinderHolder[size]; + } + }; + + public IBinder getBinder() { + return mBinder; + } + + public void setBinder(IBinder binder) { + mBinder = binder; + } + + public BinderHolder() {} + + public BinderHolder(IBinder binder) { + mBinder = binder; + } + + private BinderHolder(Parcel in) { + mBinder = in.readStrongBinder(); + } +} + diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..e535e002ef9cf9f4fb88457c35f418767d3dcea7 --- /dev/null +++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java @@ -0,0 +1,155 @@ +/* + * 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.hardware.camera2.utils; + +import static android.hardware.camera2.CameraAccessException.CAMERA_DISABLED; +import static android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED; +import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; +import static android.hardware.camera2.CameraAccessException.MAX_CAMERAS_IN_USE; +import static android.hardware.camera2.CameraAccessException.CAMERA_DEPRECATED_HAL; + +import android.os.DeadObjectException; +import android.os.RemoteException; + +import java.lang.reflect.Method; + +/** + * Translate camera service status_t return values into exceptions. + * + * @see android.hardware.camera2.utils.CameraBinderDecorator#newInstance + * @hide + */ +public class CameraBinderDecorator { + + public static final int NO_ERROR = 0; + public static final int PERMISSION_DENIED = -1; + public static final int ALREADY_EXISTS = -17; + public static final int BAD_VALUE = -22; + public static final int DEAD_OBJECT = -32; + + /** + * TODO: add as error codes in Errors.h + * - POLICY_PROHIBITS + * - RESOURCE_BUSY + * - NO_SUCH_DEVICE + */ + public static final int EACCES = -13; + public static final int EBUSY = -16; + public static final int ENODEV = -19; + public static final int EOPNOTSUPP = -95; + public static final int EUSERS = -87; + + private static class CameraBinderDecoratorListener implements Decorator.DecoratorListener { + + @Override + public void onBeforeInvocation(Method m, Object[] args) { + } + + @Override + public void onAfterInvocation(Method m, Object[] args, Object result) { + // int return type => status_t => convert to exception + if (m.getReturnType() == Integer.TYPE) { + int returnValue = (Integer) result; + + switch (returnValue) { + case NO_ERROR: + return; + case PERMISSION_DENIED: + throw new SecurityException("Lacking privileges to access camera service"); + case ALREADY_EXISTS: + // This should be handled at the call site. Typically this isn't bad, + // just means we tried to do an operation that already completed. + return; + case BAD_VALUE: + throw new IllegalArgumentException("Bad argument passed to camera service"); + case DEAD_OBJECT: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DISCONNECTED)); + case EACCES: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DISABLED)); + case EBUSY: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_IN_USE)); + case EUSERS: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + MAX_CAMERAS_IN_USE)); + case ENODEV: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DISCONNECTED)); + case EOPNOTSUPP: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DEPRECATED_HAL)); + } + + /** + * Trap the rest of the negative return values. If we have known + * error codes i.e. ALREADY_EXISTS that aren't really runtime + * errors, then add them to the top switch statement + */ + if (returnValue < 0) { + throw new UnsupportedOperationException(String.format("Unknown error %d", + returnValue)); + } + } + } + + @Override + public boolean onCatchException(Method m, Object[] args, Throwable t) { + + if (t instanceof DeadObjectException) { + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DISCONNECTED, + "Process hosting the camera service has died unexpectedly", + t)); + } else if (t instanceof RemoteException) { + throw new UnsupportedOperationException("An unknown RemoteException was thrown" + + " which should never happen.", t); + } + + return false; + } + + @Override + public void onFinally(Method m, Object[] args) { + } + + } + + /** + *

    + * Wraps the type T with a proxy that will check 'status_t' return codes + * from the native side of the camera service, and throw Java exceptions + * automatically based on the code. + *

    + *

    + * In addition it also rewrites binder's RemoteException into either a + * CameraAccessException or an UnsupportedOperationException. + *

    + *

    + * As a result of calling any method on the proxy, RemoteException is + * guaranteed never to be thrown. + *

    + * + * @param obj object that will serve as the target for all method calls + * @param the type of the element you want to wrap. This must be an interface. + * @return a proxy that will intercept all invocations to obj + */ + public static T newInstance(T obj) { + return Decorator. newInstance(obj, new CameraBinderDecoratorListener()); + } +} diff --git a/core/java/android/hardware/camera2/utils/CameraRuntimeException.java b/core/java/android/hardware/camera2/utils/CameraRuntimeException.java new file mode 100644 index 0000000000000000000000000000000000000000..9ed88a9e1c554b70205ba62aee877b761f5a5eb9 --- /dev/null +++ b/core/java/android/hardware/camera2/utils/CameraRuntimeException.java @@ -0,0 +1,63 @@ +package android.hardware.camera2.utils; + +import android.hardware.camera2.CameraAccessException; + +/** + * @hide + */ +public class CameraRuntimeException extends RuntimeException { + + private final int mReason; + private String mMessage; + private Throwable mCause; + + public final int getReason() { + return mReason; + } + + public CameraRuntimeException(int problem) { + super(); + mReason = problem; + } + + public CameraRuntimeException(int problem, String message) { + super(message); + mReason = problem; + mMessage = message; + } + + public CameraRuntimeException(int problem, String message, Throwable cause) { + super(message, cause); + mReason = problem; + mMessage = message; + mCause = cause; + } + + public CameraRuntimeException(int problem, Throwable cause) { + super(cause); + mReason = problem; + mCause = cause; + } + + /** + * Recreate this exception as the CameraAccessException equivalent. + * @return CameraAccessException + */ + public CameraAccessException asChecked() { + CameraAccessException e; + + if (mMessage != null && mCause != null) { + e = new CameraAccessException(mReason, mMessage, mCause); + } else if (mMessage != null) { + e = new CameraAccessException(mReason, mMessage); + } else if (mCause != null) { + e = new CameraAccessException(mReason, mCause); + } else { + e = new CameraAccessException(mReason); + } + // throw and catch, so java has a chance to fill out the stack trace + e.setStackTrace(this.getStackTrace()); + + return e; + } +} diff --git a/core/java/android/hardware/camera2/utils/Decorator.java b/core/java/android/hardware/camera2/utils/Decorator.java new file mode 100644 index 0000000000000000000000000000000000000000..582649712f916fe13eeff0cb880fd8204e9856bb --- /dev/null +++ b/core/java/android/hardware/camera2/utils/Decorator.java @@ -0,0 +1,92 @@ + +package android.hardware.camera2.utils; + +import java.lang.reflect.*; + +/** + * This is an implementation of the 'decorator' design pattern using Java's proxy mechanism. + * + * @see android.hardware.camera2.utils.Decorator#newInstance + * + * @hide + */ +public class Decorator implements InvocationHandler { + + public interface DecoratorListener { + /** + * This method is called before the target method is invoked + * @param args arguments to target method + * @param m Method being called + */ + void onBeforeInvocation(Method m, Object[] args); + /** + * This function is called after the target method is invoked + * if there were no uncaught exceptions + * @param args arguments to target method + * @param m Method being called + * @param result return value of target method + */ + void onAfterInvocation(Method m, Object[] args, Object result); + /** + * This method is called only if there was an exception thrown by the target method + * during its invocation. + * + * @param args arguments to target method + * @param m Method being called + * @param t Throwable that was thrown + * @return false to rethrow exception, true if the exception was handled + */ + boolean onCatchException(Method m, Object[] args, Throwable t); + /** + * This is called after the target method is invoked, regardless of whether or not + * there were any exceptions. + * @param args arguments to target method + * @param m Method being called + */ + void onFinally(Method m, Object[] args); + } + + private final T mObject; + private final DecoratorListener mListener; + + /** + * Create a decorator wrapping the specified object's method calls. + * + * @param obj the object whose method calls you want to intercept + * @param listener the decorator handler for intercepted method calls + * @param the type of the element you want to wrap. This must be an interface. + * @return a wrapped interface-compatible T + */ + @SuppressWarnings("unchecked") + public static T newInstance(T obj, DecoratorListener listener) { + return (T)java.lang.reflect.Proxy.newProxyInstance( + obj.getClass().getClassLoader(), + obj.getClass().getInterfaces(), + new Decorator(obj, listener)); + } + + private Decorator(T obj, DecoratorListener listener) { + this.mObject = obj; + this.mListener = listener; + } + + @Override + public Object invoke(Object proxy, Method m, Object[] args) + throws Throwable + { + Object result = null; + try { + mListener.onBeforeInvocation(m, args); + result = m.invoke(mObject, args); + mListener.onAfterInvocation(m, args, result); + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + if (!mListener.onCatchException(m, args, t)) { + throw t; + } + } finally { + mListener.onFinally(m, args); + } + return result; + } +} diff --git a/core/java/android/hardware/camera2/utils/UncheckedThrow.java b/core/java/android/hardware/camera2/utils/UncheckedThrow.java new file mode 100644 index 0000000000000000000000000000000000000000..8224fedba07120e9f5fc796600a4e605b827772b --- /dev/null +++ b/core/java/android/hardware/camera2/utils/UncheckedThrow.java @@ -0,0 +1,40 @@ +/* + * 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.hardware.camera2.utils; + +/** + * @hide + */ +public class UncheckedThrow { + + /** + * Throw any kind of exception without needing it to be checked + * @param e any instance of a Exception + */ + public static void throwAnyException(Exception e) { + /** + * Abuse type erasure by making the compiler think we are throwing RuntimeException, + * which is unchecked, but then inserting any exception in there. + */ + UncheckedThrow.throwAnyImpl(e); + } + + @SuppressWarnings("unchecked") + private static void throwAnyImpl(Exception e) throws T { + throw (T) e; + } +} diff --git a/core/java/android/hardware/camera2/utils/package.html b/core/java/android/hardware/camera2/utils/package.html new file mode 100644 index 0000000000000000000000000000000000000000..783d0a1b54d5275fc99f1e9673978c4fda3c8a9a --- /dev/null +++ b/core/java/android/hardware/camera2/utils/package.html @@ -0,0 +1,3 @@ + +{@hide} + diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 0a7a2e71b770aa6685b9c6508848d1d652e92ea1..f12be5f404a9abecac1e7b7036111abee15bcb3d 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Handler; import android.util.SparseArray; import android.view.Display; +import android.view.Surface; import java.util.ArrayList; @@ -68,16 +69,108 @@ public final class DisplayManager { * Display category: Presentation displays. *

    * This category can be used to identify secondary displays that are suitable for - * use as presentation displays. + * use as presentation displays such as HDMI or Wireless displays. Applications + * may automatically project their content to presentation displays to provide + * richer second screen experiences. *

    * - * @see android.app.Presentation for information about presenting content - * on secondary displays. + * @see android.app.Presentation + * @see Display#FLAG_PRESENTATION * @see #getDisplays(String) */ public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION"; + /** + * Virtual display flag: Create a public display. + * + *

    Public virtual displays

    + *

    + * When this flag is set, the virtual display is public. + *

    + * A public virtual display behaves just like most any other display that is connected + * to the system such as an HDMI or Wireless display. Applications can open + * windows on the display and the system may mirror the contents of other displays + * onto it. + *

    + * Creating a public virtual display requires the + * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT} + * or {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT} permission. + * These permissions are reserved for use by system components and are not available to + * third-party applications. + *

    + * + *

    Private virtual displays

    + *

    + * When this flag is not set, the virtual display is private as defined by the + * {@link Display#FLAG_PRIVATE} display flag. + *

    + * A private virtual display belongs to the application that created it. + * Only the a owner of a private virtual display is allowed to place windows upon it. + * The private virtual display also does not participate in display mirroring: it will + * neither receive mirrored content from another display nor allow its own content to + * be mirrored elsewhere. More precisely, the only processes that are allowed to + * enumerate or interact with the private display are those that have the same UID as the + * application that originally created the private virtual display. + *

    + * + * @see #createVirtualDisplay + */ + public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1 << 0; + + /** + * Virtual display flag: Create a presentation display. + * + *

    Presentation virtual displays

    + *

    + * When this flag is set, the virtual display is registered as a presentation + * display in the {@link #DISPLAY_CATEGORY_PRESENTATION presentation display category}. + * Applications may automatically project their content to presentation displays + * to provide richer second screen experiences. + *

    + * + *

    Non-presentation virtual displays

    + *

    + * When this flag is not set, the virtual display is not registered as a presentation + * display. Applications can still project their content on the display but they + * will typically not do so automatically. This option is appropriate for + * more special-purpose displays. + *

    + * + * @see android.app.Presentation + * @see #createVirtualDisplay + * @see #DISPLAY_CATEGORY_PRESENTATION + * @see Display#FLAG_PRESENTATION + */ + public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 1 << 1; + + /** + * Virtual display flag: Create a secure display. + * + *

    Secure virtual displays

    + *

    + * When this flag is set, the virtual display is considered secure as defined + * by the {@link Display#FLAG_SECURE} display flag. The caller promises to take + * reasonable measures, such as over-the-air encryption, to prevent the contents + * of the display from being intercepted or recorded on a persistent medium. + *

    + * Creating a secure virtual display requires the + * {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT} permission. + * This permission is reserved for use by system components and is not available to + * third-party applications. + *

    + * + *

    Non-secure virtual displays

    + *

    + * When this flag is not set, the virtual display is considered unsecure. + * The content of secure windows will be blanked if shown on this display. + *

    + * + * @see Display#FLAG_SECURE + * @see #createVirtualDisplay + */ + public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 1 << 2; + /** @hide */ public DisplayManager(Context context) { mContext = context; @@ -129,11 +222,12 @@ public final class DisplayManager { synchronized (mLock) { try { if (category == null) { - addMatchingDisplaysLocked(mTempDisplays, displayIds, -1); + addAllDisplaysLocked(mTempDisplays, displayIds); } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) { - addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI); - addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI); - addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY); + addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI); + addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI); + addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY); + addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL); } return mTempDisplays.toArray(new Display[mTempDisplays.size()]); } finally { @@ -142,12 +236,22 @@ public final class DisplayManager { } } - private void addMatchingDisplaysLocked( + private void addAllDisplaysLocked(ArrayList displays, int[] displayIds) { + for (int i = 0; i < displayIds.length; i++) { + Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/); + if (display != null) { + displays.add(display); + } + } + } + + private void addPresentationDisplaysLocked( ArrayList displays, int[] displayIds, int matchType) { for (int i = 0; i < displayIds.length; i++) { Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/); if (display != null - && (matchType < 0 || display.getType() == matchType)) { + && (display.getFlags() & Display.FLAG_PRESENTATION) != 0 + && display.getType() == matchType) { displays.add(display); } } @@ -157,7 +261,7 @@ public final class DisplayManager { Display display = mDisplays.get(displayId); if (display == null) { display = mGlobal.getCompatibleDisplay(displayId, - mContext.getCompatibilityInfo(displayId)); + mContext.getDisplayAdjustments(displayId)); if (display != null) { mDisplays.put(displayId, display); } @@ -219,6 +323,16 @@ public final class DisplayManager { mGlobal.connectWifiDisplay(deviceAddress); } + /** @hide */ + public void pauseWifiDisplay() { + mGlobal.pauseWifiDisplay(); + } + + /** @hide */ + public void resumeWifiDisplay() { + mGlobal.resumeWifiDisplay(); + } + /** * Disconnects from the current Wifi display. * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast. @@ -274,6 +388,43 @@ public final class DisplayManager { return mGlobal.getWifiDisplayStatus(); } + /** + * Creates a virtual display. + *

    + * The content of a virtual display is rendered to a {@link Surface} provided + * by the application. + *

    + * The virtual display should be {@link VirtualDisplay#release released} + * when no longer needed. Because a virtual display renders to a surface + * provided by the application, it will be released automatically when the + * process terminates and all remaining windows on it will be forcibly removed. + *

    + * The behavior of the virtual display depends on the flags that are provided + * to this method. By default, virtual displays are created to be private, + * non-presentation and unsecure. Permissions may be required to use certain flags. + *

    + * + * @param name The name of the virtual display, must be non-empty. + * @param width The width of the virtual display in pixels, must be greater than 0. + * @param height The height of the virtual display in pixels, must be greater than 0. + * @param densityDpi The density of the virtual display in dpi, must be greater than 0. + * @param surface The surface to which the content of the virtual display should + * be rendered, must be non-null. + * @param flags A combination of virtual display flags: + * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION} + * or {@link #VIRTUAL_DISPLAY_FLAG_SECURE}. + * @return The newly created virtual display, or null if the application could + * not create the virtual display. + * + * @throws SecurityException if the caller does not have permission to create + * a virtual display with the specified flags. + */ + public VirtualDisplay createVirtualDisplay(String name, + int width, int height, int densityDpi, Surface surface, int flags) { + return mGlobal.createVirtualDisplay(mContext, + name, width, height, densityDpi, surface, flags); + } + /** * Listens for changes in available display devices. */ diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index a8586814af6ede507c1677f8e94507ccb271b90d..936a0867fd83a014083f436647c262b5df143341 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -18,17 +18,20 @@ package android.hardware.display; import android.content.Context; import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; -import android.view.CompatibilityInfoHolder; +import android.view.DisplayAdjustments; import android.view.Display; import android.view.DisplayInfo; +import android.view.Surface; import java.util.ArrayList; @@ -161,18 +164,18 @@ public final class DisplayManagerGlobal { * Gets information about a logical display. * * The display metrics may be adjusted to provide compatibility - * for legacy applications. + * for legacy applications or limited screen areas. * * @param displayId The logical display id. - * @param cih The compatibility info, or null if none is required. + * @param daj The compatibility info and activityToken. * @return The display object, or null if there is no display with the given id. */ - public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) { + public Display getCompatibleDisplay(int displayId, DisplayAdjustments daj) { DisplayInfo displayInfo = getDisplayInfo(displayId); if (displayInfo == null) { return null; } - return new Display(this, displayId, displayInfo, cih); + return new Display(this, displayId, displayInfo, daj); } /** @@ -182,7 +185,18 @@ public final class DisplayManagerGlobal { * @return The display object, or null if there is no display with the given id. */ public Display getRealDisplay(int displayId) { - return getCompatibleDisplay(displayId, null); + return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + } + + /** + * Gets information about a logical display without applying any compatibility metrics. + * + * @param displayId The logical display id. + * @param IBinder the activity token for this display. + * @return The display object, or null if there is no display with the given id. + */ + public Display getRealDisplay(int displayId, IBinder token) { + return getCompatibleDisplay(displayId, new DisplayAdjustments(token)); } public void registerDisplayListener(DisplayListener listener, Handler handler) { @@ -273,6 +287,22 @@ public final class DisplayManagerGlobal { } } + public void pauseWifiDisplay() { + try { + mDm.pauseWifiDisplay(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to pause Wifi display.", ex); + } + } + + public void resumeWifiDisplay() { + try { + mDm.resumeWifiDisplay(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to resume Wifi display.", ex); + } + } + public void disconnectWifiDisplay() { try { mDm.disconnectWifiDisplay(); @@ -315,6 +345,53 @@ public final class DisplayManagerGlobal { } } + public VirtualDisplay createVirtualDisplay(Context context, String name, + int width, int height, int densityDpi, Surface surface, int flags) { + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("name must be non-null and non-empty"); + } + if (width <= 0 || height <= 0 || densityDpi <= 0) { + throw new IllegalArgumentException("width, height, and densityDpi must be " + + "greater than 0"); + } + if (surface == null) { + throw new IllegalArgumentException("surface must not be null"); + } + + Binder token = new Binder(); + int displayId; + try { + displayId = mDm.createVirtualDisplay(token, context.getPackageName(), + name, width, height, densityDpi, surface, flags); + } catch (RemoteException ex) { + Log.e(TAG, "Could not create virtual display: " + name, ex); + return null; + } + if (displayId < 0) { + Log.e(TAG, "Could not create virtual display: " + name); + return null; + } + Display display = getRealDisplay(displayId); + if (display == null) { + Log.wtf(TAG, "Could not obtain display info for newly created " + + "virtual display: " + name); + try { + mDm.releaseVirtualDisplay(token); + } catch (RemoteException ex) { + } + return null; + } + return new VirtualDisplay(this, display, token); + } + + public void releaseVirtualDisplay(IBinder token) { + try { + mDm.releaseVirtualDisplay(token); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to release virtual display.", ex); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, int event) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 79aad78d5394144b2ccb0c658d0a366ca0c138ec..6b2c887639bd34229bc6d2a48b53582cd30b90a2 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -20,6 +20,7 @@ import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.view.DisplayInfo; +import android.view.Surface; /** @hide */ interface IDisplayManager { @@ -46,4 +47,18 @@ interface IDisplayManager { // No permissions required. WifiDisplayStatus getWifiDisplayStatus(); + + // Requires CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT for certain + // combinations of flags. + int createVirtualDisplay(IBinder token, String packageName, + String name, int width, int height, int densityDpi, in Surface surface, int flags); + + // No permissions required but must be same Uid as the creator. + void releaseVirtualDisplay(in IBinder token); + + // Requires CONFIGURE_WIFI_DISPLAY permission. + void pauseWifiDisplay(); + + // Requires CONFIGURE_WIFI_DISPLAY permission. + void resumeWifiDisplay(); } diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java new file mode 100644 index 0000000000000000000000000000000000000000..01e5bac01d850eb1c04e4c0c64f259ca4d61995f --- /dev/null +++ b/core/java/android/hardware/display/VirtualDisplay.java @@ -0,0 +1,68 @@ +/* + * 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.hardware.display; + +import android.os.IBinder; +import android.view.Display; + +/** + * Represents a virtual display. The content of a virtual display is rendered to a + * {@link android.view.Surface} that you must provide to {@link DisplayManager#createVirtualDisplay + * createVirtualDisplay()}. + *

    Because a virtual display renders to a surface provided by the application, it will be + * released automatically when the process terminates and all remaining windows on it will + * be forcibly removed. However, you should also explicitly call {@link #release} when you're + * done with it. + * + * @see DisplayManager#createVirtualDisplay + */ +public final class VirtualDisplay { + private final DisplayManagerGlobal mGlobal; + private final Display mDisplay; + private IBinder mToken; + + VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token) { + mGlobal = global; + mDisplay = display; + mToken = token; + } + + /** + * Gets the virtual display. + */ + public Display getDisplay() { + return mDisplay; + } + + /** + * Releases the virtual display and destroys its underlying surface. + *

    + * All remaining windows on the virtual display will be forcibly removed + * as part of releasing the virtual display. + *

    + */ + public void release() { + if (mToken != null) { + mGlobal.releaseVirtualDisplay(mToken); + mToken = null; + } + } + + @Override + public String toString() { + return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken + "}"; + } +} diff --git a/core/java/android/hardware/display/WifiDisplay.java b/core/java/android/hardware/display/WifiDisplay.java index 2fd52b8f4e7a6094a20c33574cf45e886b5b0b18..af5a84e61116563761c52b3057eb0dfdafd561ac 100644 --- a/core/java/android/hardware/display/WifiDisplay.java +++ b/core/java/android/hardware/display/WifiDisplay.java @@ -33,6 +33,9 @@ public final class WifiDisplay implements Parcelable { private final String mDeviceAddress; private final String mDeviceName; private final String mDeviceAlias; + private final boolean mIsAvailable; + private final boolean mCanConnect; + private final boolean mIsRemembered; public static final WifiDisplay[] EMPTY_ARRAY = new WifiDisplay[0]; @@ -41,7 +44,11 @@ public final class WifiDisplay implements Parcelable { String deviceAddress = in.readString(); String deviceName = in.readString(); String deviceAlias = in.readString(); - return new WifiDisplay(deviceAddress, deviceName, deviceAlias); + boolean isAvailable = (in.readInt() != 0); + boolean canConnect = (in.readInt() != 0); + boolean isRemembered = (in.readInt() != 0); + return new WifiDisplay(deviceAddress, deviceName, deviceAlias, + isAvailable, canConnect, isRemembered); } public WifiDisplay[] newArray(int size) { @@ -49,7 +56,8 @@ public final class WifiDisplay implements Parcelable { } }; - public WifiDisplay(String deviceAddress, String deviceName, String deviceAlias) { + public WifiDisplay(String deviceAddress, String deviceName, String deviceAlias, + boolean available, boolean canConnect, boolean remembered) { if (deviceAddress == null) { throw new IllegalArgumentException("deviceAddress must not be null"); } @@ -60,6 +68,9 @@ public final class WifiDisplay implements Parcelable { mDeviceAddress = deviceAddress; mDeviceName = deviceName; mDeviceAlias = deviceAlias; + mIsAvailable = available; + mCanConnect = canConnect; + mIsRemembered = remembered; } /** @@ -87,6 +98,27 @@ public final class WifiDisplay implements Parcelable { return mDeviceAlias; } + /** + * Returns true if device is available, false otherwise. + */ + public boolean isAvailable() { + return mIsAvailable; + } + + /** + * Returns true if device can be connected to (not in use), false otherwise. + */ + public boolean canConnect() { + return mCanConnect; + } + + /** + * Returns true if device has been remembered, false otherwise. + */ + public boolean isRemembered() { + return mIsRemembered; + } + /** * Gets the name to show in the UI. * Uses the device alias if available, otherwise uses the device name. @@ -100,6 +132,10 @@ public final class WifiDisplay implements Parcelable { return o instanceof WifiDisplay && equals((WifiDisplay)o); } + /** + * Returns true if the two displays have the same identity (address, name and alias). + * This method does not compare the current status of the displays. + */ public boolean equals(WifiDisplay other) { return other != null && mDeviceAddress.equals(other.mDeviceAddress) @@ -127,6 +163,9 @@ public final class WifiDisplay implements Parcelable { dest.writeString(mDeviceAddress); dest.writeString(mDeviceName); dest.writeString(mDeviceAlias); + dest.writeInt(mIsAvailable ? 1 : 0); + dest.writeInt(mCanConnect ? 1 : 0); + dest.writeInt(mIsRemembered ? 1 : 0); } @Override @@ -141,6 +180,8 @@ public final class WifiDisplay implements Parcelable { if (mDeviceAlias != null) { result += ", alias " + mDeviceAlias; } + result += ", isAvailable " + mIsAvailable + ", canConnect " + mCanConnect + + ", isRemembered " + mIsRemembered; return result; } } diff --git a/core/java/android/hardware/display/WifiDisplaySessionInfo.java b/core/java/android/hardware/display/WifiDisplaySessionInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..33d272581f4f95c770850b17c0364c89f5e21d75 --- /dev/null +++ b/core/java/android/hardware/display/WifiDisplaySessionInfo.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains information regarding a wifi display session + * (such as session id, source ip address, etc.). This is needed for + * Wifi Display Certification process. + *

    + * This object is immutable. + *

    + * + * @hide + */ +public final class WifiDisplaySessionInfo implements Parcelable { + private final boolean mClient; + private final int mSessionId; + private final String mGroupId; + private final String mPassphrase; + private final String mIP; + + public static final Creator CREATOR = + new Creator() { + @Override + public WifiDisplaySessionInfo createFromParcel(Parcel in) { + boolean client = (in.readInt() != 0); + int session = in.readInt(); + String group = in.readString(); + String pp = in.readString(); + String ip = in.readString(); + + return new WifiDisplaySessionInfo(client, session, group, pp, ip); + } + + @Override + public WifiDisplaySessionInfo[] newArray(int size) { + return new WifiDisplaySessionInfo[size]; + } + }; + + public WifiDisplaySessionInfo() { + this(true, 0, "", "", ""); + } + + public WifiDisplaySessionInfo( + boolean client, int session, String group, String pp, String ip) { + mClient = client; + mSessionId = session; + mGroupId = group; + mPassphrase = pp; + mIP = ip; + } + + public boolean isClient() { + return mClient; + } + + public int getSessionId() { + return mSessionId; + } + + public String getGroupId() { + return mGroupId; + } + + public String getPassphrase() { + return mPassphrase; + } + + public String getIP() { + return mIP; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mClient ? 1 : 0); + dest.writeInt(mSessionId); + dest.writeString(mGroupId); + dest.writeString(mPassphrase); + dest.writeString(mIP); + } + + @Override + public int describeContents() { + return 0; + } + + // For debugging purposes only. + @Override + public String toString() { + return "WifiDisplaySessionInfo:" + +"\n Client/Owner: " + (mClient ? "Client":"Owner") + +"\n GroupId: " + mGroupId + +"\n Passphrase: " + mPassphrase + +"\n SessionId: " + mSessionId + +"\n IP Address: " + mIP + ; + } +} diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java index f7e72c417f77bc85f10d37f5974729d06a6bb0a2..52167275718a022e6cee9e4b6d60d34aa0d6baed 100644 --- a/core/java/android/hardware/display/WifiDisplayStatus.java +++ b/core/java/android/hardware/display/WifiDisplayStatus.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; /** * Describes the current global state of Wifi display connectivity, including the @@ -35,8 +37,10 @@ public final class WifiDisplayStatus implements Parcelable { private final int mScanState; private final int mActiveDisplayState; private final WifiDisplay mActiveDisplay; - private final WifiDisplay[] mAvailableDisplays; - private final WifiDisplay[] mRememberedDisplays; + private final WifiDisplay[] mDisplays; + + /** Session info needed for Miracast Certification */ + private final WifiDisplaySessionInfo mSessionInfo; /** Feature state: Wifi display is not available on this device. */ public static final int FEATURE_STATE_UNAVAILABLE = 0; @@ -70,18 +74,16 @@ public final class WifiDisplayStatus implements Parcelable { activeDisplay = WifiDisplay.CREATOR.createFromParcel(in); } - WifiDisplay[] availableDisplays = WifiDisplay.CREATOR.newArray(in.readInt()); - for (int i = 0; i < availableDisplays.length; i++) { - availableDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in); + WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(in.readInt()); + for (int i = 0; i < displays.length; i++) { + displays[i] = WifiDisplay.CREATOR.createFromParcel(in); } - WifiDisplay[] rememberedDisplays = WifiDisplay.CREATOR.newArray(in.readInt()); - for (int i = 0; i < rememberedDisplays.length; i++) { - rememberedDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in); - } + WifiDisplaySessionInfo sessionInfo = + WifiDisplaySessionInfo.CREATOR.createFromParcel(in); return new WifiDisplayStatus(featureState, scanState, activeDisplayState, - activeDisplay, availableDisplays, rememberedDisplays); + activeDisplay, displays, sessionInfo); } public WifiDisplayStatus[] newArray(int size) { @@ -91,25 +93,22 @@ public final class WifiDisplayStatus implements Parcelable { public WifiDisplayStatus() { this(FEATURE_STATE_UNAVAILABLE, SCAN_STATE_NOT_SCANNING, DISPLAY_STATE_NOT_CONNECTED, - null, WifiDisplay.EMPTY_ARRAY, WifiDisplay.EMPTY_ARRAY); + null, WifiDisplay.EMPTY_ARRAY, null); } - public WifiDisplayStatus(int featureState, int scanState, - int activeDisplayState, WifiDisplay activeDisplay, - WifiDisplay[] availableDisplays, WifiDisplay[] rememberedDisplays) { - if (availableDisplays == null) { - throw new IllegalArgumentException("availableDisplays must not be null"); - } - if (rememberedDisplays == null) { - throw new IllegalArgumentException("rememberedDisplays must not be null"); + public WifiDisplayStatus(int featureState, int scanState, int activeDisplayState, + WifiDisplay activeDisplay, WifiDisplay[] displays, WifiDisplaySessionInfo sessionInfo) { + if (displays == null) { + throw new IllegalArgumentException("displays must not be null"); } mFeatureState = featureState; mScanState = scanState; mActiveDisplayState = activeDisplayState; mActiveDisplay = activeDisplay; - mAvailableDisplays = availableDisplays; - mRememberedDisplays = rememberedDisplays; + mDisplays = displays; + + mSessionInfo = (sessionInfo != null) ? sessionInfo : new WifiDisplaySessionInfo(); } /** @@ -152,24 +151,19 @@ public final class WifiDisplayStatus implements Parcelable { } /** - * Gets the list of all available Wifi displays as reported by the most recent - * scan, never null. - *

    - * Some of these displays may already be remembered, others may be unknown. - *

    + * Gets the list of Wifi displays, returns a combined list of all available + * Wifi displays as reported by the most recent scan, and all remembered + * Wifi displays (not necessarily available at the time). */ - public WifiDisplay[] getAvailableDisplays() { - return mAvailableDisplays; + public WifiDisplay[] getDisplays() { + return mDisplays; } /** - * Gets the list of all remembered Wifi displays, never null. - *

    - * Not all remembered displays will necessarily be available. - *

    + * Gets the Wifi display session info (required for certification only) */ - public WifiDisplay[] getRememberedDisplays() { - return mRememberedDisplays; + public WifiDisplaySessionInfo getSessionInfo() { + return mSessionInfo; } @Override @@ -185,15 +179,12 @@ public final class WifiDisplayStatus implements Parcelable { dest.writeInt(0); } - dest.writeInt(mAvailableDisplays.length); - for (WifiDisplay display : mAvailableDisplays) { + dest.writeInt(mDisplays.length); + for (WifiDisplay display : mDisplays) { display.writeToParcel(dest, flags); } - dest.writeInt(mRememberedDisplays.length); - for (WifiDisplay display : mRememberedDisplays) { - display.writeToParcel(dest, flags); - } + mSessionInfo.writeToParcel(dest, flags); } @Override @@ -208,8 +199,8 @@ public final class WifiDisplayStatus implements Parcelable { + ", scanState=" + mScanState + ", activeDisplayState=" + mActiveDisplayState + ", activeDisplay=" + mActiveDisplay - + ", availableDisplays=" + Arrays.toString(mAvailableDisplays) - + ", rememberedDisplays=" + Arrays.toString(mRememberedDisplays) + + ", displays=" + Arrays.toString(mDisplays) + + ", sessionInfo=" + mSessionInfo + "}"; } } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 761faaf85938b6e03414ce60c72d9cf2e6fc1c91..30e69a68d743aae203eb66a601a60f3a124167fa 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -579,15 +579,33 @@ public final class InputManager { * @hide */ public boolean[] deviceHasKeys(int[] keyCodes) { + return deviceHasKeys(-1, keyCodes); + } + + /** + * Queries the framework about whether any physical keys exist on the + * any keyboard attached to the device that are capable of producing the given + * array of key codes. + * + * @param id The id of the device to query. + * @param keyCodes The array of key codes to query. + * @return A new array of the same size as the key codes array whose elements are set to true + * if the given device could produce the corresponding key code at the same index in the key + * codes array. + * + * @hide + */ + public boolean[] deviceHasKeys(int id, int[] keyCodes) { boolean[] ret = new boolean[keyCodes.length]; try { - mIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret); + mIm.hasKeys(id, InputDevice.SOURCE_ANY, keyCodes, ret); } catch (RemoteException e) { // no fallback; just return the empty array } return ret; } + /** * Injects an input event into the event system on behalf of an application. * The synchronization mode determines whether the method blocks while waiting for diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java index e67d0d7fd9740948ab333782104dc627ca759d8c..21de9f5df91f6df5232884857bc29ddfaa86725e 100644 --- a/core/java/android/hardware/location/GeofenceHardware.java +++ b/core/java/android/hardware/location/GeofenceHardware.java @@ -15,16 +15,11 @@ */ package android.hardware.location; -import android.content.Context; import android.location.Location; import android.os.RemoteException; -import android.util.Log; import java.lang.ref.WeakReference; import java.util.HashMap; -import java.util.Map; -import java.util.Set; - /** * This class handles geofences managed by various hardware subsystems. It contains @@ -52,13 +47,20 @@ public final class GeofenceHardware { private IGeofenceHardware mService; // Hardware systems that do geofence monitoring. - static final int NUM_MONITORS = 1; + static final int NUM_MONITORS = 2; /** * Constant for geofence monitoring done by the GPS hardware. */ public static final int MONITORING_TYPE_GPS_HARDWARE = 0; + /** + * Constant for geofence monitoring done by the Fused hardware. + * + * @hide + */ + public static final int MONITORING_TYPE_FUSED_HARDWARE = 1; + /** * Constant to indiciate that the monitoring system is currently * available for monitoring geofences. @@ -124,8 +126,12 @@ public final class GeofenceHardware { */ public static final int GEOFENCE_FAILURE = 5; - static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L; - static final int GPS_GEOFENCE_AVAILABLE = 1<<1L; + /** + * The constant used to indicate that the operation failed due to insufficient memory. + * + * @hide + */ + public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6; private HashMap mCallbacks = new HashMap(); diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java index 77e31431da299c07bbbdf40046f875b12ee14cfa..6b616909d128783cc9cb4bb62f916839e59b796d 100644 --- a/core/java/android/hardware/location/GeofenceHardwareImpl.java +++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java @@ -18,23 +18,18 @@ package android.hardware.location; import android.content.Context; import android.content.pm.PackageManager; +import android.location.IFusedGeofenceHardware; import android.location.IGpsGeofenceHardware; import android.location.Location; -import android.location.LocationManager; -import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import java.util.ArrayList; -import java.util.HashMap; /** * This class manages the geofences which are handled by hardware. @@ -54,6 +49,7 @@ public final class GeofenceHardwareImpl { new ArrayList[GeofenceHardware.NUM_MONITORS]; private final ArrayList mReapers = new ArrayList(); + private IFusedGeofenceHardware mFusedService; private IGpsGeofenceHardware mGpsService; private int[] mSupportedMonitorTypes = new int[GeofenceHardware.NUM_MONITORS]; @@ -67,7 +63,7 @@ public final class GeofenceHardwareImpl { private static final int GEOFENCE_CALLBACK_BINDER_DIED = 6; // mCallbacksHandler message types - private static final int GPS_GEOFENCE_STATUS = 1; + private static final int GEOFENCE_STATUS = 1; private static final int CALLBACK_ADD = 2; private static final int CALLBACK_REMOVE = 3; private static final int MONITOR_CALLBACK_BINDER_DIED = 4; @@ -91,16 +87,6 @@ public final class GeofenceHardwareImpl { private static final int RESOLUTION_LEVEL_COARSE = 2; private static final int RESOLUTION_LEVEL_FINE = 3; - // GPS Geofence errors. Should match gps.h constants. - private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0; - private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100; - private static final int GPS_GEOFENCE_ERROR_ID_EXISTS = -101; - private static final int GPS_GEOFENCE_ERROR_ID_UNKNOWN = -102; - private static final int GPS_GEOFENCE_ERROR_INVALID_TRANSITION = -103; - private static final int GPS_GEOFENCE_ERROR_GENERIC = -149; - - - public synchronized static GeofenceHardwareImpl getInstance(Context context) { if (sInstance == null) { sInstance = new GeofenceHardwareImpl(context); @@ -113,6 +99,9 @@ public final class GeofenceHardwareImpl { // Init everything to unsupported. setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, GeofenceHardware.MONITOR_UNSUPPORTED); + setMonitorAvailability( + GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE, + GeofenceHardware.MONITOR_UNSUPPORTED); } @@ -147,6 +136,22 @@ public final class GeofenceHardwareImpl { } } + private void updateFusedHardwareAvailability() { + boolean fusedSupported; + try { + fusedSupported = mFusedService.isSupported(); + } catch(RemoteException e) { + Log.e(TAG, "RemoteException calling LocationManagerService"); + fusedSupported = false; + } + + if(fusedSupported) { + setMonitorAvailability( + GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE, + GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE); + } + } + public void setGpsHardwareGeofence(IGpsGeofenceHardware service) { if (mGpsService == null) { mGpsService = service; @@ -159,12 +164,39 @@ public final class GeofenceHardwareImpl { } } + public void setFusedGeofenceHardware(IFusedGeofenceHardware service) { + if(mFusedService == null) { + mFusedService = service; + updateFusedHardwareAvailability(); + } else if(service == null) { + mFusedService = null; + Log.w(TAG, "Fused Geofence Hardware service seems to have crashed"); + } else { + Log.e(TAG, "Error: FusedService being set again"); + } + } + public int[] getMonitoringTypes() { + boolean gpsSupported; + boolean fusedSupported; synchronized (mSupportedMonitorTypes) { - if (mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE] != - GeofenceHardware.MONITOR_UNSUPPORTED) { - return new int[] {GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE}; + gpsSupported = mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE] + != GeofenceHardware.MONITOR_UNSUPPORTED; + fusedSupported = mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE] + != GeofenceHardware.MONITOR_UNSUPPORTED; + } + + if(gpsSupported) { + if(fusedSupported) { + return new int[] { + GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, + GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE }; + } else { + return new int[] { GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE }; } + } else if (fusedSupported) { + return new int[] { GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE }; + } else { return new int[0]; } } @@ -213,6 +245,30 @@ public final class GeofenceHardwareImpl { result = false; } break; + case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE: + if(mFusedService == null) { + return false; + } + GeofenceHardwareRequest request = GeofenceHardwareRequest.createCircularGeofence( + latitude, + longitude, + radius); + request.setUnknownTimer(unknownTimer); + request.setNotificationResponsiveness(notificationResponsivenes); + request.setMonitorTransitions(monitorTransitions); + request.setLastTransition(lastTransition); + + GeofenceHardwareRequestParcelable parcelableRequest = + new GeofenceHardwareRequestParcelable(geofenceId, request); + try { + mFusedService.addGeofences( + new GeofenceHardwareRequestParcelable[] { parcelableRequest }); + result = true; + } catch(RemoteException e) { + Log.e(TAG, "AddGeofence: RemoteException calling LocationManagerService"); + result = false; + } + break; default: result = false; } @@ -251,6 +307,18 @@ public final class GeofenceHardwareImpl { result = false; } break; + case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE: + if(mFusedService == null) { + return false; + } + try { + mFusedService.removeGeofences(new int[] { geofenceId }); + result = true; + } catch(RemoteException e) { + Log.e(TAG, "RemoveGeofence: RemoteException calling LocationManagerService"); + result = false; + } + break; default: result = false; } @@ -278,6 +346,18 @@ public final class GeofenceHardwareImpl { result = false; } break; + case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE: + if(mFusedService == null) { + return false; + } + try { + mFusedService.pauseMonitoringGeofence(geofenceId); + result = true; + } catch(RemoteException e) { + Log.e(TAG, "PauseGeofence: RemoteException calling LocationManagerService"); + result = false; + } + break; default: result = false; } @@ -306,6 +386,18 @@ public final class GeofenceHardwareImpl { result = false; } break; + case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE: + if(mFusedService == null) { + return false; + } + try { + mFusedService.resumeMonitoringGeofence(geofenceId, monitorTransition); + result = true; + } catch(RemoteException e) { + Log.e(TAG, "ResumeGeofence: RemoteException calling LocationManagerService"); + result = false; + } + break; default: result = false; } @@ -334,127 +426,106 @@ public final class GeofenceHardwareImpl { return true; } - private Location getLocation(int flags, double latitude, - double longitude, double altitude, float speed, float bearing, float accuracy, - long timestamp) { - if (DEBUG) Log.d(TAG, "GetLocation: " + flags + ":" + latitude); - Location location = new Location(LocationManager.GPS_PROVIDER); - if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { - location.setLatitude(latitude); - location.setLongitude(longitude); - location.setTime(timestamp); - // It would be nice to push the elapsed real-time timestamp - // further down the stack, but this is still useful - location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); - } - if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) { - location.setAltitude(altitude); - } else { - location.removeAltitude(); - } - if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { - location.setSpeed(speed); - } else { - location.removeSpeed(); - } - if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) { - location.setBearing(bearing); - } else { - location.removeBearing(); - } - if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { - location.setAccuracy(accuracy); - } else { - location.removeAccuracy(); - } - return location; + /** + * Used to report geofence transitions + */ + public void reportGeofenceTransition( + int geofenceId, + Location location, + int transition, + long transitionTimestamp, + int monitoringType, + int sourcesUsed) { + if(location == null) { + Log.e(TAG, String.format("Invalid Geofence Transition: location=%p", location)); + return; + } + if(DEBUG) { + Log.d( + TAG, + "GeofenceTransition| " + location + ", transition:" + transition + + ", transitionTimestamp:" + transitionTimestamp + ", monitoringType:" + + monitoringType + ", sourcesUsed:" + sourcesUsed); + } + + GeofenceTransition geofenceTransition = new GeofenceTransition( + geofenceId, + transition, + transitionTimestamp, + location, + monitoringType, + sourcesUsed); + acquireWakeLock(); + + Message message = mGeofenceHandler.obtainMessage( + GEOFENCE_TRANSITION_CALLBACK, + geofenceTransition); + message.sendToTarget(); } /** - * called from GpsLocationProvider to report geofence transition + * Used to report Monitor status changes. */ - public void reportGpsGeofenceTransition(int geofenceId, int flags, double latitude, - double longitude, double altitude, float speed, float bearing, float accuracy, - long timestamp, int transition, long transitionTimestamp) { - if (DEBUG) Log.d(TAG, "GeofenceTransition: Flags: " + flags + " Lat: " + latitude + - " Long: " + longitude + " Altitude: " + altitude + " Speed: " + speed + " Bearing: " + - bearing + " Accuracy: " + accuracy + " Timestamp: " + timestamp + " Transition: " + - transition + " TransitionTimestamp: " + transitionTimestamp); - Location location = getLocation(flags, latitude, longitude, altitude, speed, bearing, - accuracy, timestamp); - GeofenceTransition t = new GeofenceTransition(geofenceId, transition, timestamp, location); + public void reportGeofenceMonitorStatus( + int monitoringType, + int monitoringStatus, + Location location, + int source) { + // TODO: use the source if needed in the future + setMonitorAvailability(monitoringType, monitoringStatus); acquireWakeLock(); - Message m = mGeofenceHandler.obtainMessage(GEOFENCE_TRANSITION_CALLBACK, t); - mGeofenceHandler.sendMessage(m); + Message message = mCallbacksHandler.obtainMessage(GEOFENCE_STATUS, location); + message.arg1 = monitoringStatus; + message.arg2 = monitoringType; + message.sendToTarget(); } /** - * called from GpsLocationProvider to report GPS status change. + * Internal generic status report function for Geofence operations. + * + * @param operation The operation to be reported as defined internally. + * @param geofenceId The id of the geofence the operation is related to. + * @param operationStatus The status of the operation as defined in GeofenceHardware class. This + * status is independent of the statuses reported by different HALs. */ - public void reportGpsGeofenceStatus(int status, int flags, double latitude, - double longitude, double altitude, float speed, float bearing, float accuracy, - long timestamp) { - Location location = getLocation(flags, latitude, longitude, altitude, speed, bearing, - accuracy, timestamp); - boolean available = false; - if (status == GeofenceHardware.GPS_GEOFENCE_AVAILABLE) available = true; - - int val = (available ? GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE : - GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE); - setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, val); - + private void reportGeofenceOperationStatus(int operation, int geofenceId, int operationStatus) { acquireWakeLock(); - Message m = mCallbacksHandler.obtainMessage(GPS_GEOFENCE_STATUS, location); - m.arg1 = val; - mCallbacksHandler.sendMessage(m); + Message message = mGeofenceHandler.obtainMessage(operation); + message.arg1 = geofenceId; + message.arg2 = operationStatus; + message.sendToTarget(); } /** - * called from GpsLocationProvider add geofence callback. + * Used to report the status of a Geofence Add operation. */ - public void reportGpsGeofenceAddStatus(int geofenceId, int status) { - if (DEBUG) Log.d(TAG, "Add Callback: GPS : Id: " + geofenceId + " Status: " + status); - acquireWakeLock(); - Message m = mGeofenceHandler.obtainMessage(ADD_GEOFENCE_CALLBACK); - m.arg1 = geofenceId; - m.arg2 = getGeofenceStatus(status); - mGeofenceHandler.sendMessage(m); + public void reportGeofenceAddStatus(int geofenceId, int status) { + if(DEBUG) Log.d(TAG, "AddCallback| id:" + geofenceId + ", status:" + status); + reportGeofenceOperationStatus(ADD_GEOFENCE_CALLBACK, geofenceId, status); } /** - * called from GpsLocationProvider remove geofence callback. + * Used to report the status of a Geofence Remove operation. */ - public void reportGpsGeofenceRemoveStatus(int geofenceId, int status) { - if (DEBUG) Log.d(TAG, "Remove Callback: GPS : Id: " + geofenceId + " Status: " + status); - acquireWakeLock(); - Message m = mGeofenceHandler.obtainMessage(REMOVE_GEOFENCE_CALLBACK); - m.arg1 = geofenceId; - m.arg2 = getGeofenceStatus(status); - mGeofenceHandler.sendMessage(m); + public void reportGeofenceRemoveStatus(int geofenceId, int status) { + if(DEBUG) Log.d(TAG, "RemoveCallback| id:" + geofenceId + ", status:" + status); + reportGeofenceOperationStatus(REMOVE_GEOFENCE_CALLBACK, geofenceId, status); } /** - * called from GpsLocationProvider pause geofence callback. + * Used to report the status of a Geofence Pause operation. */ - public void reportGpsGeofencePauseStatus(int geofenceId, int status) { - if (DEBUG) Log.d(TAG, "Pause Callback: GPS : Id: " + geofenceId + " Status: " + status); - acquireWakeLock(); - Message m = mGeofenceHandler.obtainMessage(PAUSE_GEOFENCE_CALLBACK); - m.arg1 = geofenceId; - m.arg2 = getGeofenceStatus(status); - mGeofenceHandler.sendMessage(m); + public void reportGeofencePauseStatus(int geofenceId, int status) { + if(DEBUG) Log.d(TAG, "PauseCallbac| id:" + geofenceId + ", status" + status); + reportGeofenceOperationStatus(PAUSE_GEOFENCE_CALLBACK, geofenceId, status); } /** - * called from GpsLocationProvider resume geofence callback. + * Used to report the status of a Geofence Resume operation. */ - public void reportGpsGeofenceResumeStatus(int geofenceId, int status) { - if (DEBUG) Log.d(TAG, "Resume Callback: GPS : Id: " + geofenceId + " Status: " + status); - acquireWakeLock(); - Message m = mGeofenceHandler.obtainMessage(RESUME_GEOFENCE_CALLBACK); - m.arg1 = geofenceId; - m.arg2 = getGeofenceStatus(status); - mGeofenceHandler.sendMessage(m); + public void reportGeofenceResumeStatus(int geofenceId, int status) { + if(DEBUG) Log.d(TAG, "ResumeCallback| id:" + geofenceId + ", status:" + status); + reportGeofenceOperationStatus(RESUME_GEOFENCE_CALLBACK, geofenceId, status); } // All operations on mGeofences @@ -527,19 +598,20 @@ public final class GeofenceHardwareImpl { GeofenceTransition geofenceTransition = (GeofenceTransition)(msg.obj); synchronized (mGeofences) { callback = mGeofences.get(geofenceTransition.mGeofenceId); - } - if (DEBUG) Log.d(TAG, "GeofenceTransistionCallback: GPS : GeofenceId: " + - geofenceTransition.mGeofenceId + - " Transition: " + geofenceTransition.mTransition + - " Location: " + geofenceTransition.mLocation + ":" + mGeofences); + // need to keep access to mGeofences synchronized at all times + if (DEBUG) Log.d(TAG, "GeofenceTransistionCallback: GPS : GeofenceId: " + + geofenceTransition.mGeofenceId + + " Transition: " + geofenceTransition.mTransition + + " Location: " + geofenceTransition.mLocation + ":" + mGeofences); + } if (callback != null) { try { callback.onGeofenceTransition( geofenceTransition.mGeofenceId, geofenceTransition.mTransition, geofenceTransition.mLocation, geofenceTransition.mTimestamp, - GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE); + geofenceTransition.mMonitoringType); } catch (RemoteException e) {} } releaseWakeLock(); @@ -571,21 +643,20 @@ public final class GeofenceHardwareImpl { IGeofenceHardwareMonitorCallback callback; switch (msg.what) { - case GPS_GEOFENCE_STATUS: + case GEOFENCE_STATUS: Location location = (Location) msg.obj; int val = msg.arg1; + monitoringType = msg.arg2; boolean available; available = (val == GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE ? true : false); - callbackList = mCallbacks[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE]; + callbackList = mCallbacks[monitoringType]; if (callbackList != null) { if (DEBUG) Log.d(TAG, "MonitoringSystemChangeCallback: GPS : " + available); for (IGeofenceHardwareMonitorCallback c: callbackList) { try { - c.onMonitoringSystemChange( - GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, available, - location); + c.onMonitoringSystemChange(monitoringType, available, location); } catch (RemoteException e) {} } } @@ -666,12 +737,22 @@ public final class GeofenceHardwareImpl { private int mGeofenceId, mTransition; private long mTimestamp; private Location mLocation; - - GeofenceTransition(int geofenceId, int transition, long timestamp, Location location) { + private int mMonitoringType; + private int mSourcesUsed; + + GeofenceTransition( + int geofenceId, + int transition, + long timestamp, + Location location, + int monitoringType, + int sourcesUsed) { mGeofenceId = geofenceId; mTransition = transition; mTimestamp = timestamp; mLocation = location; + mMonitoringType = monitoringType; + mSourcesUsed = sourcesUsed; } } @@ -686,6 +767,8 @@ public final class GeofenceHardwareImpl { switch (monitoringType) { case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE: return RESOLUTION_LEVEL_FINE; + case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE: + return RESOLUTION_LEVEL_FINE; } return RESOLUTION_LEVEL_NONE; } @@ -752,22 +835,4 @@ public final class GeofenceHardwareImpl { return RESOLUTION_LEVEL_NONE; } } - - private int getGeofenceStatus(int status) { - switch (status) { - case GPS_GEOFENCE_OPERATION_SUCCESS: - return GeofenceHardware.GEOFENCE_SUCCESS; - case GPS_GEOFENCE_ERROR_GENERIC: - return GeofenceHardware.GEOFENCE_FAILURE; - case GPS_GEOFENCE_ERROR_ID_EXISTS: - return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS; - case GPS_GEOFENCE_ERROR_INVALID_TRANSITION: - return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION; - case GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES: - return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES; - case GPS_GEOFENCE_ERROR_ID_UNKNOWN: - return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN; - } - return -1; - } } diff --git a/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b599d44fcfed73b5410b063c7477596f70681153 --- /dev/null +++ b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl @@ -0,0 +1,19 @@ +/* + * 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.hardware.location; + +parcelable GeofenceHardwareRequestParcelable; diff --git a/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java new file mode 100644 index 0000000000000000000000000000000000000000..40e7fc44d720de28b59bd95d83f5f02ac8d7e820 --- /dev/null +++ b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java @@ -0,0 +1,151 @@ +/* + * 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.hardware.location; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * Geofence Hardware Request used for internal location services communication. + * + * @hide + */ +public final class GeofenceHardwareRequestParcelable implements Parcelable { + private GeofenceHardwareRequest mRequest; + private int mId; + + public GeofenceHardwareRequestParcelable(int id, GeofenceHardwareRequest request) { + mId = id; + mRequest = request; + } + + /** + * Returns the id of this request. + */ + public int getId() { + return mId; + } + + /** + * Returns the latitude of this geofence. + */ + public double getLatitude() { + return mRequest.getLatitude(); + } + + /** + * Returns the longitude of this geofence. + */ + public double getLongitude() { + return mRequest.getLongitude(); + } + + /** + * Returns the radius of this geofence. + */ + public double getRadius() { + return mRequest.getRadius(); + } + + /** + * Returns transitions monitored for this geofence. + */ + public int getMonitorTransitions() { + return mRequest.getMonitorTransitions(); + } + + /** + * Returns the unknownTimer of this geofence. + */ + public int getUnknownTimer() { + return mRequest.getUnknownTimer(); + } + + /** + * Returns the notification responsiveness of this geofence. + */ + public int getNotificationResponsiveness() { + return mRequest.getNotificationResponsiveness(); + } + + /** + * Returns the last transition of this geofence. + */ + public int getLastTransition() { + return mRequest.getLastTransition(); + } + + /** + * Returns the type of the geofence for the current request. + */ + int getType() { + return mRequest.getType(); + } + + /** + * Method definitions to support Parcelable operations. + */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public GeofenceHardwareRequestParcelable createFromParcel(Parcel parcel) { + int geofenceType = parcel.readInt(); + if(geofenceType != GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) { + Log.e( + "GeofenceHardwareRequest", + String.format("Invalid Geofence type: %d", geofenceType)); + return null; + } + + GeofenceHardwareRequest request = GeofenceHardwareRequest.createCircularGeofence( + parcel.readDouble(), + parcel.readDouble(), + parcel.readDouble()); + request.setLastTransition(parcel.readInt()); + request.setMonitorTransitions(parcel.readInt()); + request.setUnknownTimer(parcel.readInt()); + request.setNotificationResponsiveness(parcel.readInt()); + + int id = parcel.readInt(); + return new GeofenceHardwareRequestParcelable(id, request); + } + + @Override + public GeofenceHardwareRequestParcelable[] newArray(int size) { + return new GeofenceHardwareRequestParcelable[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(getType()); + parcel.writeDouble(getLatitude()); + parcel.writeDouble(getLongitude()); + parcel.writeDouble(getRadius()); + parcel.writeInt(getLastTransition()); + parcel.writeInt(getMonitorTransitions()); + parcel.writeInt(getUnknownTimer()); + parcel.writeInt(getNotificationResponsiveness()); + parcel.writeInt(getId()); + } +} diff --git a/core/java/android/hardware/location/GeofenceHardwareService.java b/core/java/android/hardware/location/GeofenceHardwareService.java index 3bc70eed749ffaf986a57215ee49d0dc466a902b..fb238bd392d9194747b54b0b2a871722716bdeb1 100644 --- a/core/java/android/hardware/location/GeofenceHardwareService.java +++ b/core/java/android/hardware/location/GeofenceHardwareService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.location.IFusedGeofenceHardware; import android.location.IGpsGeofenceHardware; import android.os.Binder; import android.os.IBinder; @@ -68,6 +69,10 @@ public class GeofenceHardwareService extends Service { mGeofenceHardwareImpl.setGpsHardwareGeofence(service); } + public void setFusedGeofenceHardware(IFusedGeofenceHardware service) { + mGeofenceHardwareImpl.setFusedGeofenceHardware(service); + } + public int[] getMonitoringTypes() { mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, "Location Hardware permission not granted to access hardware geofence"); diff --git a/core/java/android/hardware/location/IFusedLocationHardware.aidl b/core/java/android/hardware/location/IFusedLocationHardware.aidl new file mode 100644 index 0000000000000000000000000000000000000000..382c12c85be17ed1add20361772e667846c447cb --- /dev/null +++ b/core/java/android/hardware/location/IFusedLocationHardware.aidl @@ -0,0 +1,117 @@ +/* + * 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/license/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.location; + +import android.hardware.location.IFusedLocationHardwareSink; +import android.location.FusedBatchOptions; + +/** + * Fused Location hardware interface. + * This interface is the basic set of supported functionality by Fused Hardware + * modules that offer Location batching capabilities. + * + * @hide + */ +interface IFusedLocationHardware { + /** + * Registers a sink with the Location Hardware object. + * + * @param eventSink The sink to register. + */ + void registerSink(in IFusedLocationHardwareSink eventSink); + + /** + * Unregisters a sink with the Location Hardware object. + * + * @param eventSink The sink to unregister. + */ + void unregisterSink(in IFusedLocationHardwareSink eventSink); + + /** + * Provides access to the batch size available in Hardware. + * + * @return The batch size the hardware supports. + */ + int getSupportedBatchSize(); + + /** + * Requests the Hardware to start batching locations. + * + * @param id An Id associated with the request. + * @param batchOptions The options required for batching. + * + * @throws RuntimeException if the request Id exists. + */ + void startBatching(in int id, in FusedBatchOptions batchOptions); + + /** + * Requests the Hardware to stop batching for the given Id. + * + * @param id The request that needs to be stopped. + * @throws RuntimeException if the request Id is unknown. + */ + void stopBatching(in int id); + + /** + * Updates a batching operation in progress. + * + * @param id The Id of the operation to update. + * @param batchOptions The options to apply to the given operation. + * + * @throws RuntimeException if the Id of the request is unknown. + */ + void updateBatchingOptions(in int id, in FusedBatchOptions batchOptions); + + /** + * Requests the most recent locations available in Hardware. + * This operation does not dequeue the locations, so still other batching + * events will continue working. + * + * @param batchSizeRequested The number of locations requested. + */ + void requestBatchOfLocations(in int batchSizeRequested); + + /** + * Flags if the Hardware supports injection of diagnostic data. + * + * @return True if data injection is supported, false otherwise. + */ + boolean supportsDiagnosticDataInjection(); + + /** + * Injects diagnostic data into the Hardware subsystem. + * + * @param data The data to inject. + * @throws RuntimeException if injection is not supported. + */ + void injectDiagnosticData(in String data); + + /** + * Flags if the Hardware supports injection of device context information. + * + * @return True if device context injection is supported, false otherwise. + */ + boolean supportsDeviceContextInjection(); + + /** + * Injects device context information into the Hardware subsystem. + * + * @param deviceEnabledContext The context to inject. + * @throws RuntimeException if injection is not supported. + */ + void injectDeviceContext(in int deviceEnabledContext); +} diff --git a/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl b/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a11d8abb2ffbcf161558d4b29e4942e1002486ca --- /dev/null +++ b/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl @@ -0,0 +1,41 @@ +/* + * 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/license/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.location; + +import android.location.Location; + +/** + * Fused Location hardware event sink interface. + * This interface defines the set of events that the FusedLocationHardware provides. + * + * @hide + */ +interface IFusedLocationHardwareSink { + /** + * Event generated when a batch of location information is available. + * + * @param locations The batch of location information available. + */ + void onLocationAvailable(in Location[] locations); + + /** + * Event generated from FLP HAL to provide diagnostic data to the platform. + * + * @param data The diagnostic data provided by FLP HAL. + */ + void onDiagnosticDataAvailable(in String data); +} \ No newline at end of file diff --git a/core/java/android/hardware/location/IGeofenceHardware.aidl b/core/java/android/hardware/location/IGeofenceHardware.aidl index 6900070caf93c371c58732d5934197a2dcba43ae..890016638698143cd51aa8f09b153d12dd0b33d8 100644 --- a/core/java/android/hardware/location/IGeofenceHardware.aidl +++ b/core/java/android/hardware/location/IGeofenceHardware.aidl @@ -16,6 +16,7 @@ package android.hardware.location; +import android.location.IFusedGeofenceHardware; import android.location.IGpsGeofenceHardware; import android.hardware.location.IGeofenceHardwareCallback; import android.hardware.location.IGeofenceHardwareMonitorCallback; @@ -23,6 +24,7 @@ import android.hardware.location.IGeofenceHardwareMonitorCallback; /** @hide */ interface IGeofenceHardware { void setGpsGeofenceHardware(in IGpsGeofenceHardware service); + void setFusedGeofenceHardware(in IFusedGeofenceHardware service); int[] getMonitoringTypes(); int getStatusOfMonitoringType(int monitoringType); boolean addCircularFence(int id, int monitoringType, double lat, double longitude, diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 93063739cfd6eabadb42becf3373180087cb6904..06d8e4a8cb2e27d4ba72d7b2924dc773b4cd8679 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -279,6 +279,10 @@ class IInputMethodWrapper extends IInputMethod.Stub try { InputMethodSession ls = ((IInputMethodSessionWrapper) session).getInternalInputMethodSession(); + if (ls == null) { + Log.w(TAG, "Session is already finished: " + session); + return; + } mCaller.executeOrSendMessage(mCaller.obtainMessageIO( DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls)); } catch (ClassCastException e) { @@ -291,6 +295,10 @@ class IInputMethodWrapper extends IInputMethod.Stub try { InputMethodSession ls = ((IInputMethodSessionWrapper) session).getInternalInputMethodSession(); + if (ls == null) { + Log.w(TAG, "Session is already finished: " + session); + return; + } mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls)); } catch (ClassCastException e) { Log.w(TAG, "Incoming session not of correct type: " + session, e); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 4881d1433489765e5ff1637d5aa7763547779181..1b7d9eab9fc1d4c8b848b44326d35faf30efa74e 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -381,7 +381,6 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding + " ic=" + mInputConnection); onUnbindInput(); - mInputStarted = false; mInputBinding = null; mInputConnection = null; } @@ -688,6 +687,8 @@ public class InputMethodService extends AbstractInputMethodService { mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService); mRootView = mInflater.inflate( com.android.internal.R.layout.input_method, null); + mRootView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); mWindow.setContentView(mRootView); mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); if (Settings.Global.getInt(getContentResolver(), @@ -719,7 +720,7 @@ public class InputMethodService extends AbstractInputMethodService { super.onDestroy(); mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); - finishViews(); + doFinishInput(); if (mWindowAdded) { // Disable exit animation for the current IME window // to avoid the race condition between the exit and enter animations @@ -1651,6 +1652,11 @@ public class InputMethodService extends AbstractInputMethodService { * the text. This is called whether or not the input method has requested * extracted text updates, although if so it will not receive this call * if the extracted text has changed as well. + * + *

    Be careful about changing the text in reaction to this call with + * methods such as setComposingText, commitText or + * deleteSurroundingText. If the cursor moves as a result, this method + * will be called again, which may result in an infinite loop. * *

    The default implementation takes care of updating the cursor in * the extract text, if it is being shown. diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java index 1165281e39bb77317d571b6e40acffd44caf1ef2..476fefe33cc82489745215c2ad8ecbdc5351a56b 100644 --- a/core/java/android/net/BaseNetworkStateTracker.java +++ b/core/java/android/net/BaseNetworkStateTracker.java @@ -57,6 +57,10 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker { mLinkCapabilities = new LinkCapabilities(); } + protected BaseNetworkStateTracker() { + // By default, let the sub classes construct everything + } + @Deprecated protected Handler getTargetHandler() { return mTarget; @@ -73,34 +77,46 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker { } @Override - public final void startMonitoring(Context context, Handler target) { + public void startMonitoring(Context context, Handler target) { mContext = Preconditions.checkNotNull(context); mTarget = Preconditions.checkNotNull(target); startMonitoringInternal(); } - protected abstract void startMonitoringInternal(); + protected void startMonitoringInternal() { + + } @Override - public final NetworkInfo getNetworkInfo() { + public NetworkInfo getNetworkInfo() { return new NetworkInfo(mNetworkInfo); } @Override - public final LinkProperties getLinkProperties() { + public LinkProperties getLinkProperties() { return new LinkProperties(mLinkProperties); } @Override - public final LinkCapabilities getLinkCapabilities() { + public LinkCapabilities getLinkCapabilities() { return new LinkCapabilities(mLinkCapabilities); } + @Override + public LinkQualityInfo getLinkQualityInfo() { + return null; + } + @Override public void captivePortalCheckComplete() { // not implemented } + @Override + public void captivePortalCheckCompleted(boolean isCaptivePortal) { + // not implemented + } + @Override public boolean setRadio(boolean turnOn) { // Base tracker doesn't handle radios @@ -171,4 +187,23 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker { public void supplyMessenger(Messenger messenger) { // not supported on this network } + + @Override + public String getNetworkInterfaceName() { + if (mLinkProperties != null) { + return mLinkProperties.getInterfaceName(); + } else { + return null; + } + } + + @Override + public void startSampling(SamplingDataTracker.SamplingSnapshot s) { + // nothing to do + } + + @Override + public void stopSampling(SamplingDataTracker.SamplingSnapshot s) { + // nothing to do + } } diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java index 21995c021ca504b07daaabfe64e966ad056a6bfc..d678f1e99b80d7ca3de6a09eb678d5d14775843d 100644 --- a/core/java/android/net/CaptivePortalTracker.java +++ b/core/java/android/net/CaptivePortalTracker.java @@ -16,23 +16,29 @@ 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; import android.provider.Settings; +import android.telephony.CellIdentityCdma; +import android.telephony.CellIdentityGsm; +import android.telephony.CellIdentityLte; +import android.telephony.CellIdentityWcdma; +import android.telephony.CellInfo; +import android.telephony.CellInfoCdma; +import android.telephony.CellInfoGsm; +import android.telephony.CellInfoLte; +import android.telephony.CellInfoWcdma; import android.telephony.TelephonyManager; import com.android.internal.util.State; @@ -42,30 +48,45 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.Inet4Address; +import java.net.SocketTimeoutException; import java.net.URL; import java.net.UnknownHostException; - -import com.android.internal.R; +import java.util.List; /** * This class allows captive portal detection on a network. * @hide */ public class CaptivePortalTracker extends StateMachine { - private static final boolean DBG = false; + private static final boolean DBG = true; 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; + public static final String ACTION_NETWORK_CONDITIONS_MEASURED = + "android.net.conn.NETWORK_CONDITIONS_MEASURED"; + public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type"; + public static final String EXTRA_NETWORK_TYPE = "extra_network_type"; + public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received"; + public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal"; + public static final String EXTRA_CELL_ID = "extra_cellid"; + public static final String EXTRA_SSID = "extra_ssid"; + public static final String EXTRA_BSSID = "extra_bssid"; + /** real time since boot */ + public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms"; + public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms"; + + private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS = + "android.permission.ACCESS_NETWORK_CONDITIONS"; + private String mServer; private String mUrl; - private boolean mNotificationShown = false; private boolean mIsCaptivePortalCheckEnabled = false; private IConnectivityManager mConnService; private TelephonyManager mTelephonyManager; + private WifiManager mWifiManager; private Context mContext; private NetworkInfo mNetworkInfo; @@ -92,6 +113,7 @@ public class CaptivePortalTracker extends StateMachine { mContext = context; mConnService = cs; mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mProvisioningObserver = new ProvisioningObserver(); IntentFilter filter = new IntentFilter(); @@ -157,14 +179,10 @@ public class CaptivePortalTracker extends StateMachine { } private class DefaultState extends State { - @Override - public void enter() { - if (DBG) log(getName() + "\n"); - } @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; @@ -186,23 +204,25 @@ public class CaptivePortalTracker extends StateMachine { private class NoActiveNetworkState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + setNotificationOff(); 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: @@ -213,11 +233,6 @@ public class CaptivePortalTracker extends StateMachine { } private class ActiveNetworkState extends State { - @Override - public void enter() { - if (DBG) log(getName() + "\n"); - } - @Override public boolean processMessage(Message message) { NetworkInfo info; @@ -248,7 +263,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); @@ -259,9 +273,11 @@ 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: + setNotificationOff(); + if (message.arg1 == mDelayedCheckToken) { InetAddress server = lookupHost(mServer); boolean captive = server != null && isCaptivePortal(server); @@ -270,11 +286,17 @@ public class CaptivePortalTracker extends StateMachine { } else { if (DBG) log("Not captive network " + mNetworkInfo); } + notifyPortalCheckCompleted(mNetworkInfo, captive); if (mDeviceProvisioned) { 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( @@ -300,12 +322,26 @@ public class CaptivePortalTracker extends StateMachine { return; } try { + if (DBG) log("notifyPortalCheckComplete: ni=" + info); mConnService.captivePortalCheckComplete(info); } catch(RemoteException e) { e.printStackTrace(); } } + private void notifyPortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) { + if (info == null) { + loge("notifyPortalCheckComplete on null"); + return; + } + try { + if (DBG) log("notifyPortalCheckCompleted: captive=" + isCaptivePortal + " ni=" + info); + mConnService.captivePortalCheckCompleted(info, isCaptivePortal); + } catch(RemoteException e) { + e.printStackTrace(); + } + } + private boolean isActiveNetwork(NetworkInfo info) { try { NetworkInfo active = mConnService.getActiveNetworkInfo(); @@ -318,8 +354,20 @@ public class CaptivePortalTracker extends StateMachine { return false; } + private void setNotificationOff() { + try { + if (mNetworkInfo != null) { + mConnService.setProvisioningNotificationVisible(false, mNetworkInfo.getType(), + 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 + * Do a URL fetch on a known server to see if we get the data we expect. + * Measure the response time and broadcast that. */ private boolean isCaptivePortal(InetAddress server) { HttpURLConnection urlConnection = null; @@ -327,6 +375,7 @@ public class CaptivePortalTracker extends StateMachine { mUrl = "http://" + server.getHostAddress() + "/generate_204"; if (DBG) log("Checking " + mUrl); + long requestTimestamp = -1; try { URL url = new URL(mUrl); urlConnection = (HttpURLConnection) url.openConnection(); @@ -334,11 +383,29 @@ public class CaptivePortalTracker extends StateMachine { urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); urlConnection.setUseCaches(false); + + // Time how long it takes to get a response to our request + requestTimestamp = SystemClock.elapsedRealtime(); + urlConnection.getInputStream(); + + // Time how long it takes to get a response to our request + long responseTimestamp = SystemClock.elapsedRealtime(); + // we got a valid response, but not from the real google - return 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 (IOException e) { if (DBG) log("Probably not a portal: exception " + e); + if (requestTimestamp != -1) { + sendFailedCaptivePortalCheckBroadcast(requestTimestamp); + } // else something went wrong with setting up the urlConnection return false; } finally { if (urlConnection != null) { @@ -352,66 +419,91 @@ public class CaptivePortalTracker extends StateMachine { try { inetAddress = InetAddress.getAllByName(hostname); } catch (UnknownHostException e) { + sendFailedCaptivePortalCheckBroadcast(SystemClock.elapsedRealtime()); return null; } for (InetAddress a : inetAddress) { if (a instanceof Inet4Address) return a; } + + sendFailedCaptivePortalCheckBroadcast(SystemClock.elapsedRealtime()); return null; } - private void setNotificationVisible(boolean visible) { - // if it should be hidden and it is already hidden, then noop - if (!visible && !mNotificationShown) { + private void sendFailedCaptivePortalCheckBroadcast(long requestTimestampMs) { + sendNetworkConditionsBroadcast(false /* response received */, false /* ignored */, + requestTimestampMs, 0 /* ignored */); + } + + /** + * @param responseReceived - whether or not we received a valid HTTP response to our request. + * If false, isCaptivePortal and responseTimestampMs are ignored + */ + private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, + long requestTimestampMs, long responseTimestampMs) { + if (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) { + if (DBG) log("Don't send network conditions - lacking user consent."); return; } - Resources r = Resources.getSystem(); - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (visible) { - CharSequence title; - CharSequence details; - int icon; - 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; - 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; - 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; - break; - } + Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED); + switch (mNetworkInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: + WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); + if (currentWifiInfo != null) { + latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID()); + latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID()); + } else { + if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); + return; + } + break; + case ConnectivityManager.TYPE_MOBILE: + latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType()); + List info = mTelephonyManager.getAllCellInfo(); + if (info == null) return; + StringBuffer uniqueCellId = new StringBuffer(); + int numRegisteredCellInfo = 0; + for (CellInfo cellInfo : info) { + if (cellInfo.isRegistered()) { + numRegisteredCellInfo++; + if (numRegisteredCellInfo > 1) { + if (DBG) log("more than one registered CellInfo. Can't " + + "tell which is active. Bailing."); + return; + } + if (cellInfo instanceof CellInfoCdma) { + CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); + } else if (cellInfo instanceof CellInfoGsm) { + CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); + } else if (cellInfo instanceof CellInfoLte) { + CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); + } else if (cellInfo instanceof CellInfoWcdma) { + CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); + } else { + if (DBG) logw("Registered cellinfo is unrecognized"); + return; + } + } + } + break; + default: + return; + } + latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkInfo.getType()); + latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived); + latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs); - 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(mUrl)); - 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); - - notificationManager.notify(NOTIFICATION_ID, 1, notification); - } else { - notificationManager.cancel(NOTIFICATION_ID, 1); + if (responseReceived) { + latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal); + latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs); } - mNotificationShown = visible; + mContext.sendBroadcast(latencyBroadcast, PERMISSION_ACCESS_NETWORK_CONDITIONS); } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 697bde99a2d1f5e901cd67a6f4535debe72d4568..c78a973ceeb0fc06387d03f08bea90a077db981b 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -355,11 +355,17 @@ public class ConnectivityManager { */ public static final int TYPE_WIFI_P2P = 13; + /** + * The network to use for initially attaching to the network + * {@hide} + */ + public static final int TYPE_MOBILE_IA = 14; + /** {@hide} */ - public static final int MAX_RADIO_TYPE = TYPE_WIFI_P2P; + public static final int MAX_RADIO_TYPE = TYPE_MOBILE_IA; /** {@hide} */ - public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P; + public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_IA; /** * If you want to set the default network preference,you can directly @@ -436,6 +442,8 @@ public class ConnectivityManager { return "MOBILE_CBS"; case TYPE_WIFI_P2P: return "WIFI_P2P"; + case TYPE_MOBILE_IA: + return "MOBILE_IA"; default: return Integer.toString(type); } @@ -458,6 +466,39 @@ public class ConnectivityManager { case TYPE_MOBILE_FOTA: case TYPE_MOBILE_IMS: case TYPE_MOBILE_CBS: + case TYPE_MOBILE_IA: + return true; + default: + return false; + } + } + + /** + * Checks if the given network type is backed by a Wi-Fi radio. + * + * @hide + */ + public static boolean isNetworkTypeWifi(int networkType) { + switch (networkType) { + case TYPE_WIFI: + case TYPE_WIFI_P2P: + return true; + default: + return false; + } + } + + /** + * Checks if the given network type should be exempt from VPN routing rules + * + * @hide + */ + public static boolean isNetworkTypeExempt(int networkType) { + switch (networkType) { + case TYPE_MOBILE_MMS: + case TYPE_MOBILE_SUPL: + case TYPE_MOBILE_HIPRI: + case TYPE_MOBILE_IA: return true; default: return false; @@ -582,6 +623,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 + * + *

    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. * @@ -1282,6 +1346,25 @@ public class ConnectivityManager { } } + /** + * Signal that the captive portal check on the indicated network + * is complete and whether its a captive portal or not. + * + * @param info the {@link NetworkInfo} object for the networkType + * in question. + * @param isCaptivePortal true/false. + * + *

    This method requires the call to hold the permission + * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}. + * {@hide} + */ + public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) { + try { + mService.captivePortalCheckCompleted(info, isCaptivePortal); + } catch (RemoteException e) { + } + } + /** * Supply the backend messenger for a network tracker * @@ -1297,77 +1380,116 @@ public class ConnectivityManager { } /** - * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE) + * Check mobile provisioning. + * + * @param suggestedTimeOutMs, timeout in milliseconds + * + * @return time out that will be used, maybe less that suggestedTimeOutMs + * -1 if an error. + * + * {@hide} */ + public int checkMobileProvisioning(int suggestedTimeOutMs) { + int timeOutMs = -1; + try { + timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs); + } catch (RemoteException e) { + } + return timeOutMs; + } /** - * No connection was possible to the network. + * Get the mobile provisioning url. * {@hide} */ - public static final int CMP_RESULT_CODE_NO_CONNECTION = 0; + public String getMobileProvisioningUrl() { + try { + return mService.getMobileProvisioningUrl(); + } catch (RemoteException e) { + } + return null; + } /** - * A connection was made to the internet, all is well. + * Get the mobile redirected provisioning url. * {@hide} */ - public static final int CMP_RESULT_CODE_CONNECTABLE = 1; + public String getMobileRedirectedProvisioningUrl() { + try { + return mService.getMobileRedirectedProvisioningUrl(); + } catch (RemoteException e) { + } + return null; + } /** - * 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} + * get the information about a specific network link + * @hide */ - public static final int CMP_RESULT_CODE_REDIRECTED = 2; + public LinkQualityInfo getLinkQualityInfo(int networkType) { + try { + LinkQualityInfo li = mService.getLinkQualityInfo(networkType); + return li; + } catch (RemoteException e) { + return null; + } + } /** - * 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. - * - * {@hide} + * get the information of currently active network link + * @hide */ - public static final int CMP_RESULT_CODE_NO_DNS = 3; + public LinkQualityInfo getActiveLinkQualityInfo() { + try { + LinkQualityInfo li = mService.getActiveLinkQualityInfo(); + return li; + } catch (RemoteException e) { + return null; + } + } /** - * A connection was made but could not open a TCP connection. - * This is an indication of a warm sim on a mobile network. - * {@hide} + * get the information of all network links + * @hide */ - public static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 4; + public LinkQualityInfo[] getAllLinkQualityInfo() { + try { + LinkQualityInfo[] li = mService.getAllLinkQualityInfo(); + return li; + } catch (RemoteException e) { + return null; + } + } /** - * 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 + * Set sign in error notification to visible or in visible * - * @return time out that will be used, maybe less that suggestedTimeOutMs - * -1 if an error. + * @param visible + * @param networkType * * {@hide} */ - public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, - ResultReceiver resultReceiver) { - int timeOutMs = -1; + public void setProvisioningNotificationVisible(boolean visible, int networkType, + String extraInfo, String url) { try { - timeOutMs = mService.checkMobileProvisioning(sendNotification, suggestedTimeOutMs, - resultReceiver); + mService.setProvisioningNotificationVisible(visible, networkType, extraInfo, url); } catch (RemoteException e) { } - return timeOutMs; } /** - * Get the carrier provisioning url. - * {@hide} + * Set the value for enabling/disabling airplane mode + * + * @param enable whether to enable airplane mode or not + * + *

    This method requires the call to hold the permission + * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}. + * @hide */ - public String getMobileProvisioningUrl() { + public void setAirplaneMode(boolean enable) { try { - return mService.getMobileProvisioningUrl(); + mService.setAirplaneMode(enable); } catch (RemoteException e) { } - return null; } } diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java index ab4cd9b9ef7601c761572f12bc2aca2251e8d493..3bede5da50902b0edef247058f76f4c158b57641 100644 --- a/core/java/android/net/DhcpInfo.java +++ b/core/java/android/net/DhcpInfo.java @@ -22,7 +22,6 @@ import java.net.InetAddress; /** * A simple object for retrieving the results of a DHCP request. - * @deprecated - use LinkProperties - To be removed 11/2014 */ public class DhcpInfo implements Parcelable { public int ipAddress; diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index 1ebf393afa3441c458473c6478ec749d6b9187a0..5151a04b577bfeb9d5cab092e55de0e99f970d9d 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -374,7 +374,7 @@ public class DhcpStateMachine extends StateMachine { //Do it a bit earlier than half the lease duration time //to beat the native DHCP client and avoid extra packets //48% for one hour lease time = 29 minutes - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + leaseDuration * 480, //in milliseconds mDhcpRenewalIntent); diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java index 15a81f3e45e8bdcae4c3aabbf638de4faec79a2f..51a1191fd49e6aaca768c758a29f92efacff5ef4 100644 --- a/core/java/android/net/DummyDataStateTracker.java +++ b/core/java/android/net/DummyDataStateTracker.java @@ -29,18 +29,14 @@ import android.util.Slog; * * {@hide} */ -public class DummyDataStateTracker implements NetworkStateTracker { +public class DummyDataStateTracker extends BaseNetworkStateTracker { private static final String TAG = "DummyDataStateTracker"; private static final boolean DBG = true; private static final boolean VDBG = false; - 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; @@ -120,10 +116,16 @@ public class DummyDataStateTracker implements NetworkStateTracker { return true; } + @Override public void captivePortalCheckComplete() { // not implemented } + @Override + public void captivePortalCheckCompleted(boolean isCaptivePortal) { + // not implemented + } + /** * Record the detailed state of a network, and if it is a * change from the previous state, send a notification to diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index 8a7e292b2c6eb3280a19548ea7beea20dc0d8df7..cc8c77193eca8758f2d759f94e0dd1b506f5d0a0 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -27,6 +27,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import com.android.server.net.BaseNetworkObserver; + import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -36,7 +38,7 @@ import java.util.concurrent.atomic.AtomicInteger; * ConnectivityService. * @hide */ -public class EthernetDataTracker implements NetworkStateTracker { +public class EthernetDataTracker extends BaseNetworkStateTracker { private static final String NETWORKTYPE = "ETHERNET"; private static final String TAG = "Ethernet"; @@ -46,15 +48,11 @@ public class EthernetDataTracker implements NetworkStateTracker { private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false); private static boolean mLinkUp; - private LinkProperties mLinkProperties; - private LinkCapabilities mLinkCapabilities; - private NetworkInfo mNetworkInfo; private InterfaceObserver mInterfaceObserver; private String mHwAddr; /* For sending events to connectivity service handler */ private Handler mCsHandler; - private Context mContext; private static EthernetDataTracker sInstance; private static String sIfaceMatch = ""; @@ -62,7 +60,7 @@ public class EthernetDataTracker implements NetworkStateTracker { private INetworkManagementService mNMService; - private static class InterfaceObserver extends INetworkManagementEventObserver.Stub { + private static class InterfaceObserver extends BaseNetworkObserver { private EthernetDataTracker mTracker; InterfaceObserver(EthernetDataTracker tracker) { @@ -70,10 +68,12 @@ public class EthernetDataTracker implements NetworkStateTracker { mTracker = tracker; } + @Override public void interfaceStatusChanged(String iface, boolean up) { Log.d(TAG, "Interface status changed: " + iface + (up ? "up" : "down")); } + @Override public void interfaceLinkStateChanged(String iface, boolean up) { if (mIface.equals(iface)) { Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down")); @@ -89,21 +89,15 @@ public class EthernetDataTracker implements NetworkStateTracker { } } + @Override public void interfaceAdded(String iface) { mTracker.interfaceAdded(iface); } + @Override public void interfaceRemoved(String iface) { mTracker.interfaceRemoved(iface); } - - public void limitReached(String limitName, String iface) { - // Ignored. - } - - public void interfaceClassDataActivityChanged(String label, boolean active) { - // Ignored. - } } private EthernetDataTracker() { @@ -280,6 +274,11 @@ public class EthernetDataTracker implements NetworkStateTracker { // not implemented } + @Override + public void captivePortalCheckCompleted(boolean isCaptivePortal) { + // not implemented + } + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 4600c1a4dc27e6ace4dac033ca297f13739dc861..c1da2e32aa009aa6c08c1e3c24223a58aa4e95c7 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -16,6 +16,7 @@ package android.net; +import android.net.LinkQualityInfo; import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkQuotaInfo; @@ -37,6 +38,9 @@ import com.android.internal.net.VpnProfile; /** {@hide} */ interface IConnectivityManager { + // Keep this in sync with framework/native/services/connectivitymanager/ConnectivityManager.h + void markSocketAsUser(in ParcelFileDescriptor socket, int uid); + void setNetworkPreference(int pref); int getNetworkPreference(); @@ -46,6 +50,8 @@ interface IConnectivityManager NetworkInfo getNetworkInfo(int networkType); NetworkInfo[] getAllNetworkInfo(); + NetworkInfo getProvisioningOrActiveNetworkInfo(); + boolean isNetworkSupported(int networkType); LinkProperties getActiveLinkProperties(); @@ -87,12 +93,6 @@ interface IConnectivityManager String[] getTetheredIfaces(); - /** - * Return list of interface pairs that are actively tethered. Even indexes are - * remote interface, and odd indexes are corresponding local interfaces. - */ - String[] getTetheredIfacePairs(); - String[] getTetheringErroredIfaces(); String[] getTetherableUsbRegexs(); @@ -121,6 +121,8 @@ interface IConnectivityManager ParcelFileDescriptor establishVpn(in VpnConfig config); + VpnConfig getVpnConfig(); + void startLegacyVpn(in VpnProfile profile); LegacyVpnInfo getLegacyVpnInfo(); @@ -129,11 +131,25 @@ interface IConnectivityManager void captivePortalCheckComplete(in NetworkInfo info); + void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal); + void supplyMessenger(int networkType, in Messenger messenger); int findConnectionTypeForIface(in String iface); - int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, in ResultReceiver resultReceiver); + int checkMobileProvisioning(int suggestedTimeOutMs); String getMobileProvisioningUrl(); + + String getMobileRedirectedProvisioningUrl(); + + LinkQualityInfo getLinkQualityInfo(int networkType); + + LinkQualityInfo getActiveLinkQualityInfo(); + + LinkQualityInfo[] getAllLinkQualityInfo(); + + void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo, in String url); + + void setAirplaneMode(boolean enable); } diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index 6f4dd5f348ebdc67641c022df4cecbd8345bada6..b76e4c24599c663012d32be8978af1bd953efada 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -53,6 +53,27 @@ interface INetworkManagementEventObserver { */ void interfaceRemoved(String iface); + + /** + * An interface address has been added or updated + * + * @param address The address. + * @param iface The interface. + * @param flags The address flags. + * @param scope The address scope. + */ + void addressUpdated(String address, String iface, int flags, int scope); + + /** + * An interface address has been removed + * + * @param address The address. + * @param iface The interface. + * @param flags The address flags. + * @param scope The address scope. + */ + void addressRemoved(String address, String iface, int flags, int scope); + /** * A networking quota limit has been reached. The quota might not * be specific to an interface. diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index f6a114c83402d7d675e4e553156d262c21e9b036..a390add89ebd65212523c33bc15befd5450bb293 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -32,27 +32,56 @@ public class LinkAddress implements Parcelable { /** * IPv4 or IPv6 address. */ - private final InetAddress address; + private InetAddress address; /** * Network prefix length */ - private final int prefixLength; + private int prefixLength; - public LinkAddress(InetAddress address, int prefixLength) { + private void init(InetAddress address, int prefixLength) { if (address == null || prefixLength < 0 || ((address instanceof Inet4Address) && prefixLength > 32) || (prefixLength > 128)) { throw new IllegalArgumentException("Bad LinkAddress params " + address + - prefixLength); + "/" + prefixLength); } this.address = address; this.prefixLength = prefixLength; } + public LinkAddress(InetAddress address, int prefixLength) { + init(address, prefixLength); + } + public LinkAddress(InterfaceAddress interfaceAddress) { - this.address = interfaceAddress.getAddress(); - this.prefixLength = interfaceAddress.getNetworkPrefixLength(); + init(interfaceAddress.getAddress(), + interfaceAddress.getNetworkPrefixLength()); + } + + /** + * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or + * "2001:db8::1/64". + * @param string The string to parse. + */ + public LinkAddress(String address) { + InetAddress inetAddress = null; + int prefixLength = -1; + try { + String [] pieces = address.split("/", 2); + prefixLength = Integer.parseInt(pieces[1]); + inetAddress = InetAddress.parseNumericAddress(pieces[0]); + } catch (NullPointerException e) { // Null string. + } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. + } catch (NumberFormatException e) { // Non-numeric prefix. + } catch (IllegalArgumentException e) { // Invalid IP address. + } + + if (inetAddress == null || prefixLength == -1) { + throw new IllegalArgumentException("Bad LinkAddress params " + address); + } + + init(inetAddress, prefixLength); } @Override diff --git a/core/java/android/net/LinkProperties.aidl b/core/java/android/net/LinkProperties.aidl index 9cd06d57844ef8a74437c49319c05b75fa4dde36..3cb9525761ed359f88e23aba335016b966850f21 100644 --- a/core/java/android/net/LinkProperties.aidl +++ b/core/java/android/net/LinkProperties.aidl @@ -18,4 +18,3 @@ package android.net; parcelable LinkProperties; - diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 75f8b5948b815da051cbb5a5f30f512ded6d92c1..b4d07a1ec23c954cbe211a8d51900adb024473bd 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import java.net.InetAddress; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.UnknownHostException; import java.util.ArrayList; @@ -65,6 +66,7 @@ public class LinkProperties implements Parcelable { private String mDomains; private Collection mRoutes = new ArrayList(); private ProxyProperties mHttpProxy; + private int mMtu; // Stores the properties of links that are "stacked" above this link. // Indexed by interface name to allow modification and to prevent duplicates being added. @@ -103,6 +105,7 @@ public class LinkProperties implements Parcelable { for (LinkProperties l: source.mStackedLinks.values()) { addStackedLink(l); } + setMtu(source.getMtu()); } } @@ -128,6 +131,9 @@ public class LinkProperties implements Parcelable { return interfaceNames; } + /** + * Returns all the addresses on this link. + */ public Collection getAddresses() { Collection addresses = new ArrayList(); for (LinkAddress linkAddress : mLinkAddresses) { @@ -136,14 +142,73 @@ public class LinkProperties implements Parcelable { return Collections.unmodifiableCollection(addresses); } - public void addLinkAddress(LinkAddress address) { - if (address != null) mLinkAddresses.add(address); + /** + * Returns all the addresses on this link and all the links stacked above it. + */ + public Collection getAllAddresses() { + Collection addresses = new ArrayList(); + for (LinkAddress linkAddress : mLinkAddresses) { + addresses.add(linkAddress.getAddress()); + } + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllAddresses()); + } + return addresses; } + /** + * Adds a link address if it does not exist, or update it if it does. + * @param address The {@code LinkAddress} to add. + * @return true if the address was added, false if it already existed. + */ + public boolean addLinkAddress(LinkAddress address) { + // TODO: when the LinkAddress has other attributes beyond the + // address and the prefix length, update them here. + if (address != null && !mLinkAddresses.contains(address)) { + mLinkAddresses.add(address); + return true; + } + return false; + } + + /** + * Removes a link address. + * @param address The {@code LinkAddress} to remove. + * @return true if the address was removed, false if it did not exist. + */ + public boolean removeLinkAddress(LinkAddress toRemove) { + return mLinkAddresses.remove(toRemove); + } + + /** + * Returns all the addresses on this link. + */ public Collection getLinkAddresses() { return Collections.unmodifiableCollection(mLinkAddresses); } + /** + * Returns all the addresses on this link and all the links stacked above it. + */ + public Collection getAllLinkAddresses() { + Collection addresses = new ArrayList(); + addresses.addAll(mLinkAddresses); + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllLinkAddresses()); + } + return addresses; + } + + /** + * Replaces the LinkAddresses on this link with the given collection of addresses. + */ + public void setLinkAddresses(Collection addresses) { + mLinkAddresses.clear(); + for (LinkAddress address: addresses) { + addLinkAddress(address); + } + } + public void addDns(InetAddress dns) { if (dns != null) mDnses.add(dns); } @@ -160,6 +225,14 @@ public class LinkProperties implements Parcelable { mDomains = domains; } + public void setMtu(int mtu) { + mMtu = mtu; + } + + public int getMtu() { + return mMtu; + } + private RouteInfo routeWithInterface(RouteInfo route) { return new RouteInfo( route.getDestination(), @@ -213,11 +286,14 @@ public class LinkProperties implements Parcelable { * of stacked links. If link is null, nothing changes. * * @param link The link to add. + * @return true if the link was stacked, false otherwise. */ - public void addStackedLink(LinkProperties link) { + public boolean addStackedLink(LinkProperties link) { if (link != null && link.getInterfaceName() != null) { mStackedLinks.put(link.getInterfaceName(), link); + return true; } + return false; } /** @@ -226,12 +302,15 @@ public class LinkProperties implements Parcelable { * If there a stacked link with the same interfacename as link, it is * removed. Otherwise, nothing changes. * - * @param link The link to add. + * @param link The link to remove. + * @return true if the link was removed, false otherwise. */ - public void removeStackedLink(LinkProperties link) { + public boolean removeStackedLink(LinkProperties link) { if (link != null && link.getInterfaceName() != null) { - mStackedLinks.remove(link.getInterfaceName()); + LinkProperties removed = mStackedLinks.remove(link.getInterfaceName()); + return removed != null; } + return false; } /** @@ -253,6 +332,7 @@ public class LinkProperties implements Parcelable { mRoutes.clear(); mHttpProxy = null; mStackedLinks.clear(); + mMtu = 0; } /** @@ -277,6 +357,8 @@ public class LinkProperties implements Parcelable { String domainName = "Domains: " + mDomains; + String mtu = "MTU: " + mMtu; + String routes = " Routes: ["; for (RouteInfo route : mRoutes) routes += route.toString() + ","; routes += "] "; @@ -290,7 +372,8 @@ public class LinkProperties implements Parcelable { } stacked += "] "; } - return "{" + ifaceName + linkAddresses + routes + dns + domainName + proxy + stacked + "}"; + return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu + + proxy + stacked + "}"; } /** @@ -307,6 +390,20 @@ public class LinkProperties implements Parcelable { return false; } + /** + * Returns true if this link has an IPv6 address. + * + * @return {@code true} if there is an IPv6 address, {@code false} otherwise. + */ + public boolean hasIPv6Address() { + for (LinkAddress address : mLinkAddresses) { + if (address.getAddress() instanceof Inet6Address) { + return true; + } + } + return false; + } + /** * Compares this {@code LinkProperties} interface name against the target * @@ -391,6 +488,16 @@ public class LinkProperties implements Parcelable { return true; } + /** + * Compares this {@code LinkProperties} MTU against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + */ + public boolean isIdenticalMtu(LinkProperties target) { + return getMtu() == target.getMtu(); + } + @Override /** * Compares this {@code LinkProperties} instance against the target @@ -422,17 +529,16 @@ public class LinkProperties implements Parcelable { isIdenticalDnses(target) && isIdenticalRoutes(target) && isIdenticalHttpProxy(target) && - isIdenticalStackedLinks(target); + isIdenticalStackedLinks(target) && + isIdenticalMtu(target); } /** - * Return two lists, a list of addresses that would be removed from - * mLinkAddresses and a list of addresses that would be added to - * mLinkAddress which would then result in target and mLinkAddresses - * being the same list. + * Compares the addresses in this LinkProperties with another + * LinkProperties, examining only addresses on the base link. * - * @param target is a LinkProperties with the new list of addresses - * @return the removed and added lists. + * @param target a LinkProperties with the new list of addresses + * @return the differences between the addresses. */ public CompareResult compareAddresses(LinkProperties target) { /* @@ -456,13 +562,11 @@ public class LinkProperties implements Parcelable { } /** - * Return two lists, a list of dns addresses that would be removed from - * mDnses and a list of addresses that would be added to - * mDnses which would then result in target and mDnses - * being the same list. + * Compares the DNS addresses in this LinkProperties with another + * LinkProperties, examining only DNS addresses on the base link. * - * @param target is a LinkProperties with the new list of dns addresses - * @return the removed and added lists. + * @param target a LinkProperties with the new list of dns addresses + * @return the differences between the DNS addresses. */ public CompareResult compareDnses(LinkProperties target) { /* @@ -487,15 +591,13 @@ public class LinkProperties implements Parcelable { } /** - * Return two lists, a list of routes that would be removed from - * mRoutes and a list of routes that would be added to - * mRoutes which would then result in target and mRoutes - * being the same list. + * Compares all routes in this LinkProperties with another LinkProperties, + * examining both the the base link and all stacked links. * - * @param target is a LinkProperties with the new list of routes - * @return the removed and added lists. + * @param target a LinkProperties with the new list of routes + * @return the differences between the routes. */ - public CompareResult compareRoutes(LinkProperties target) { + public CompareResult compareAllRoutes(LinkProperties target) { /* * Duplicate the RouteInfos into removed, we will be removing * routes which are common between mRoutes and target @@ -530,7 +632,8 @@ public class LinkProperties implements Parcelable { + ((null == mDomains) ? 0 : mDomains.hashCode()) + mRoutes.size() * 41 + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()) - + mStackedLinks.hashCode() * 47); + + mStackedLinks.hashCode() * 47) + + mMtu * 51; } /** @@ -548,7 +651,7 @@ public class LinkProperties implements Parcelable { dest.writeByteArray(d.getAddress()); } dest.writeString(mDomains); - + dest.writeInt(mMtu); dest.writeInt(mRoutes.size()); for(RouteInfo route : mRoutes) { dest.writeParcelable(route, flags); @@ -587,6 +690,7 @@ public class LinkProperties implements Parcelable { } catch (UnknownHostException e) { } } netProp.setDomains(in.readString()); + netProp.setMtu(in.readInt()); addressCount = in.readInt(); for (int i=0; i CREATOR = + new Creator() { + public LinkQualityInfo createFromParcel(Parcel in) { + int objectType = in.readInt(); + if (objectType == OBJECT_TYPE_LINK_QUALITY_INFO) { + LinkQualityInfo li = new LinkQualityInfo(); + li.initializeFromParcel(in); + return li; + } else if (objectType == OBJECT_TYPE_WIFI_LINK_QUALITY_INFO) { + return WifiLinkQualityInfo.createFromParcelBody(in); + } else if (objectType == OBJECT_TYPE_MOBILE_LINK_QUALITY_INFO) { + return MobileLinkQualityInfo.createFromParcelBody(in); + } else { + return null; + } + } + + public LinkQualityInfo[] newArray(int size) { + return new LinkQualityInfo[size]; + } + }; + + /** + * @hide + */ + protected void initializeFromParcel(Parcel in) { + mNetworkType = in.readInt(); + mNormalizedSignalStrength = in.readInt(); + mPacketCount = in.readLong(); + mPacketErrorCount = in.readLong(); + mTheoreticalTxBandwidth = in.readInt(); + mTheoreticalRxBandwidth = in.readInt(); + mTheoreticalLatency = in.readInt(); + mLastDataSampleTime = in.readLong(); + mDataSampleDuration = in.readInt(); + } + + /** + * returns the type of network this link is connected to + * @return network type as defined by {@link android.net.ConnectivityManager} or + * {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getNetworkType() { + return mNetworkType; + } + + /** + * @hide + */ + public void setNetworkType(int networkType) { + mNetworkType = networkType; + } + + /** + * returns the signal strength normalized across multiple types of networks + * @return an integer value from 0 - 99 or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getNormalizedSignalStrength() { + return mNormalizedSignalStrength; + } + + /** + * @hide + */ + public void setNormalizedSignalStrength(int normalizedSignalStrength) { + mNormalizedSignalStrength = normalizedSignalStrength; + } + + /** + * returns the total number of packets sent or received in sample duration + * @return number of packets or {@link android.net.LinkQualityInfo#UNKNOWN_LONG} + */ + public long getPacketCount() { + return mPacketCount; + } + + /** + * @hide + */ + public void setPacketCount(long packetCount) { + mPacketCount = packetCount; + } + + /** + * returns the total number of packets errors encountered in sample duration + * @return number of errors or {@link android.net.LinkQualityInfo#UNKNOWN_LONG} + */ + public long getPacketErrorCount() { + return mPacketErrorCount; + } + + /** + * @hide + */ + public void setPacketErrorCount(long packetErrorCount) { + mPacketErrorCount = packetErrorCount; + } + + /** + * returns the theoretical upload bandwidth of this network + * @return bandwidth in Kbps or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getTheoreticalTxBandwidth() { + return mTheoreticalTxBandwidth; + } + + /** + * @hide + */ + public void setTheoreticalTxBandwidth(int theoreticalTxBandwidth) { + mTheoreticalTxBandwidth = theoreticalTxBandwidth; + } + + /** + * returns the theoretical download bandwidth of this network + * @return bandwidth in Kbps or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getTheoreticalRxBandwidth() { + return mTheoreticalRxBandwidth; + } + + /** + * @hide + */ + public void setTheoreticalRxBandwidth(int theoreticalRxBandwidth) { + mTheoreticalRxBandwidth = theoreticalRxBandwidth; + } + + /** + * returns the theoretical latency of this network + * @return latency in milliseconds or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getTheoreticalLatency() { + return mTheoreticalLatency; + } + + /** + * @hide + */ + public void setTheoreticalLatency(int theoreticalLatency) { + mTheoreticalLatency = theoreticalLatency; + } + + /** + * returns the time stamp of the last sample + * @return milliseconds elapsed since start and sample time or + * {@link android.net.LinkQualityInfo#UNKNOWN_LONG} + */ + public long getLastDataSampleTime() { + return mLastDataSampleTime; + } + + /** + * @hide + */ + public void setLastDataSampleTime(long lastDataSampleTime) { + mLastDataSampleTime = lastDataSampleTime; + } + + /** + * returns the sample duration used + * @return duration in milliseconds or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getDataSampleDuration() { + return mDataSampleDuration; + } + + /** + * @hide + */ + public void setDataSampleDuration(int dataSampleDuration) { + mDataSampleDuration = dataSampleDuration; + } +} diff --git a/core/java/android/net/LocalServerSocket.java b/core/java/android/net/LocalServerSocket.java index 2b93fc2235fc16dc10848a83a2c036a34f7fa7cf..a36203be9f408b603e17aae7b99ceb63557cad19 100644 --- a/core/java/android/net/LocalServerSocket.java +++ b/core/java/android/net/LocalServerSocket.java @@ -46,7 +46,7 @@ public class LocalServerSocket { { impl = new LocalSocketImpl(); - impl.create(true); + impl.create(LocalSocket.SOCKET_STREAM); localAddress = new LocalSocketAddress(name); impl.bind(localAddress); @@ -93,7 +93,7 @@ public class LocalServerSocket { impl.accept (acceptedImpl); - return new LocalSocket(acceptedImpl); + return new LocalSocket(acceptedImpl, LocalSocket.SOCKET_UNKNOWN); } /** diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java index 14a80948b7977615341891ae178675ed6a0c21d6..31bc20b03501b12d82c92666823c83a6cea85616 100644 --- a/core/java/android/net/LocalSocket.java +++ b/core/java/android/net/LocalSocket.java @@ -34,21 +34,42 @@ public class LocalSocket implements Closeable { private LocalSocketAddress localAddress; private boolean isBound; private boolean isConnected; + private final int sockType; + + /** unknown socket type (used for constructor with existing file descriptor) */ + /* package */ static final int SOCKET_UNKNOWN = 0; + /** Datagram socket type */ + public static final int SOCKET_DGRAM = 1; + /** Stream socket type */ + public static final int SOCKET_STREAM = 2; + /** Sequential packet socket type */ + public static final int SOCKET_SEQPACKET = 3; /** * Creates a AF_LOCAL/UNIX domain stream socket. */ public LocalSocket() { - this(new LocalSocketImpl()); + this(SOCKET_STREAM); + } + + /** + * Creates a AF_LOCAL/UNIX domain stream socket with given socket type + * + * @param sockType either {@link #SOCKET_DGRAM}, {@link #SOCKET_STREAM} + * or {@link #SOCKET_SEQPACKET} + */ + public LocalSocket(int sockType) { + this(new LocalSocketImpl(), sockType); isBound = false; isConnected = false; } + /** * Creates a AF_LOCAL/UNIX domain stream socket with FileDescriptor. * @hide */ public LocalSocket(FileDescriptor fd) throws IOException { - this(new LocalSocketImpl(fd)); + this(new LocalSocketImpl(fd), SOCKET_UNKNOWN); isBound = true; isConnected = true; } @@ -57,8 +78,9 @@ public class LocalSocket implements Closeable { * for use with AndroidServerSocket * @param impl a SocketImpl */ - /*package*/ LocalSocket(LocalSocketImpl impl) { + /*package*/ LocalSocket(LocalSocketImpl impl, int sockType) { this.impl = impl; + this.sockType = sockType; this.isConnected = false; this.isBound = false; } @@ -81,7 +103,7 @@ public class LocalSocket implements Closeable { synchronized (this) { if (!implCreated) { try { - impl.create(true); + impl.create(sockType); } finally { implCreated = true; } diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java index 3b43c36c43a914c15a4c77ce67fd2f54373fdc10..b2ee50afc6080d2d563b0f493d80b09165925425 100644 --- a/core/java/android/net/LocalSocketImpl.java +++ b/core/java/android/net/LocalSocketImpl.java @@ -22,6 +22,10 @@ import java.io.InputStream; import java.io.FileDescriptor; import java.net.SocketOptions; +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.OsConstants; + /** * Socket implementation used for android.net.LocalSocket and * android.net.LocalServerSocket. Supports only AF_LOCAL sockets. @@ -35,6 +39,8 @@ class LocalSocketImpl /** null if closed or not yet created */ private FileDescriptor fd; + /** whether fd is created internally */ + private boolean mFdCreatedInternally; // These fields are accessed by native code; /** file descriptor array received during a previous read */ @@ -159,7 +165,6 @@ class LocalSocketImpl private native int pending_native(FileDescriptor fd) throws IOException; private native int available_native(FileDescriptor fd) throws IOException; - private native void close_native(FileDescriptor fd) throws IOException; private native int read_native(FileDescriptor fd) throws IOException; private native int readba_native(byte[] b, int off, int len, FileDescriptor fd) throws IOException; @@ -171,8 +176,6 @@ class LocalSocketImpl int namespace) throws IOException; private native void bindLocal(FileDescriptor fd, String name, int namespace) throws IOException; - private native FileDescriptor create_native(boolean stream) - throws IOException; private native void listen_native(FileDescriptor fd, int backlog) throws IOException; private native void shutdown(FileDescriptor fd, boolean shutdownInput); @@ -222,15 +225,34 @@ class LocalSocketImpl /** * Creates a socket in the underlying OS. * - * @param stream true if this should be a stream socket, false for - * datagram. + * @param sockType either {@link LocalSocket#SOCKET_DGRAM}, {@link LocalSocket#SOCKET_STREAM} + * or {@link LocalSocket#SOCKET_SEQPACKET} * @throws IOException */ - public void create (boolean stream) throws IOException { + public void create (int sockType) throws IOException { // no error if socket already created // need this for LocalServerSocket.accept() if (fd == null) { - fd = create_native(stream); + int osType; + switch (sockType) { + case LocalSocket.SOCKET_DGRAM: + osType = OsConstants.SOCK_DGRAM; + break; + case LocalSocket.SOCKET_STREAM: + osType = OsConstants.SOCK_STREAM; + break; + case LocalSocket.SOCKET_SEQPACKET: + osType = OsConstants.SOCK_SEQPACKET; + break; + default: + throw new IllegalStateException("unknown sockType"); + } + try { + fd = Libcore.os.socket(OsConstants.AF_UNIX, osType, 0); + mFdCreatedInternally = true; + } catch (ErrnoException e) { + e.rethrowAsIOException(); + } } } @@ -241,8 +263,15 @@ class LocalSocketImpl */ public void close() throws IOException { synchronized (LocalSocketImpl.this) { - if (fd == null) return; - close_native(fd); + if ((fd == null) || (mFdCreatedInternally == false)) { + fd = null; + return; + } + try { + Libcore.os.close(fd); + } catch (ErrnoException e) { + e.rethrowAsIOException(); + } fd = null; } } diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index 5a1daed9511a87c91d2f311a2aac93a3f22495a5..c1065145dbf27dd666b4794cc16bcad67598c1c0 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -28,6 +28,8 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.PhoneStateListener; +import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; @@ -40,6 +42,7 @@ import com.android.internal.util.AsyncChannel; import java.io.CharArrayWriter; import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicBoolean; /** * Track the state of mobile data connectivity. This is done by @@ -48,10 +51,10 @@ import java.io.PrintWriter; * * {@hide} */ -public class MobileDataStateTracker implements NetworkStateTracker { +public class MobileDataStateTracker extends BaseNetworkStateTracker { private static final String TAG = "MobileDataStateTracker"; - private static final boolean DBG = false; + private static final boolean DBG = true; private static final boolean VDBG = false; private PhoneConstants.DataState mMobileDataState; @@ -75,6 +78,14 @@ public class MobileDataStateTracker implements NetworkStateTracker { private Handler mHandler; private AsyncChannel mDataConnectionTrackerAc; + private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false); + + private SignalStrength mSignalStrength; + + private SamplingDataTracker mSamplingDataTracker = new SamplingDataTracker(); + + private static final int UNKNOWN = LinkQualityInfo.UNKNOWN_INT; + /** * Create a new MobileDataStateTracker * @param netType the ConnectivityManager network type @@ -101,12 +112,24 @@ public class MobileDataStateTracker implements NetworkStateTracker { 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); mMobileDataState = PhoneConstants.DataState.DISCONNECTED; + + TelephonyManager tm = (TelephonyManager)mContext.getSystemService( + Context.TELEPHONY_SERVICE); + tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); } + private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mSignalStrength = signalStrength; + } + }; + static class MdstHandler extends Handler { private MobileDataStateTracker mMdst; @@ -168,10 +191,47 @@ public class MobileDataStateTracker implements NetworkStateTracker { 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(); + } + mLinkProperties.setMtu(mContext.getResources().getInteger( + com.android.internal.R.integer.config_mobile_mtu)); + 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); + mNetworkInfo.setIsConnectedToProvisioningNetwork(true); + + // Change state to SUSPENDED so setDetailedState + // sends EVENT_STATE_CHANGED to connectivityService + setDetailedState(DetailedState.SUSPENDED, "", apnName); + } else if (intent.getAction().equals(TelephonyIntents. ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY); if (VDBG) { @@ -182,6 +242,11 @@ public class MobileDataStateTracker implements NetworkStateTracker { if (!TextUtils.equals(apnType, mApnType)) { return; } + // Assume this isn't a provisioning network. + mNetworkInfo.setIsConnectedToProvisioningNetwork(false); + if (DBG) { + log("Broadcast received: " + intent.getAction() + " apnType=" + apnType); + } int oldSubtype = mNetworkInfo.getSubtype(); int newSubType = TelephonyManager.getDefault().getNetworkType(); @@ -233,21 +298,34 @@ public class MobileDataStateTracker implements NetworkStateTracker { 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; } + + if (VDBG) { + Slog.d(TAG, "TelephonyMgr.DataConnectionStateChanged"); + if (mNetworkInfo != null) { + Slog.d(TAG, "NetworkInfo = " + mNetworkInfo.toString()); + Slog.d(TAG, "subType = " + String.valueOf(mNetworkInfo.getSubtype())); + Slog.d(TAG, "subType = " + mNetworkInfo.getSubtypeName()); + } + if (mLinkProperties != null) { + Slog.d(TAG, "LinkProperties = " + mLinkProperties.toString()); + } else { + Slog.d(TAG, "LinkProperties = " ); + } + + if (mLinkCapabilities != null) { + Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities.toString()); + } else { + Slog.d(TAG, "LinkCapabilities = " ); + } + } + + + /* lets not sample traffic data across state changes */ + mSamplingDataTracker.resetSamplingData(); } else { // There was no state change. Check if LinkProperties has been updated. if (TextUtils.equals(reason, PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) { @@ -276,11 +354,13 @@ public class MobileDataStateTracker implements NetworkStateTracker { } return; } + // Assume this isn't a provisioning network. + mNetworkInfo.setIsConnectedToProvisioningNetwork(false); 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 { @@ -372,11 +452,27 @@ public class MobileDataStateTracker implements NetworkStateTracker { 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 } + @Override + public void captivePortalCheckCompleted(boolean isCaptivePortal) { + if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) { + // Captive portal change enable/disable failing fast + setEnableFailFastMobileData( + isCaptivePortal ? DctConstants.ENABLED : DctConstants.DISABLED); + } + } + /** * Record the detailed state of a network, and if it is a * change from the previous state, send a notification to @@ -525,6 +621,40 @@ public class MobileDataStateTracker implements NetworkStateTracker { } } + /** + * 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); @@ -545,7 +675,7 @@ public class MobileDataStateTracker implements NetworkStateTracker { return writer.toString(); } - /** + /** * Internal method supporting the ENABLE_MMS feature. * @param apnType the type of APN to be enabled or disabled (e.g., mms) * @param enable {@code true} to enable the specified APN type, @@ -597,15 +727,19 @@ public class MobileDataStateTracker implements NetworkStateTracker { return PhoneConstants.APN_TYPE_IMS; case ConnectivityManager.TYPE_MOBILE_CBS: return PhoneConstants.APN_TYPE_CBS; + case ConnectivityManager.TYPE_MOBILE_IA: + return PhoneConstants.APN_TYPE_IA; default: sloge("Error mapping networkType " + netType + " to apnType."); return null; } } + /** * @see android.net.NetworkStateTracker#getLinkProperties() */ + @Override public LinkProperties getLinkProperties() { return new LinkProperties(mLinkProperties); } @@ -613,6 +747,7 @@ public class MobileDataStateTracker implements NetworkStateTracker { /** * @see android.net.NetworkStateTracker#getLinkCapabilities() */ + @Override public LinkCapabilities getLinkCapabilities() { return new LinkCapabilities(mLinkCapabilities); } @@ -634,4 +769,152 @@ public class MobileDataStateTracker implements NetworkStateTracker { static private void sloge(String s) { Slog.e(TAG, s); } + + @Override + public LinkQualityInfo getLinkQualityInfo() { + if (mNetworkInfo == null || mNetworkInfo.getType() == ConnectivityManager.TYPE_NONE) { + // no data available yet; just return + return null; + } + + MobileLinkQualityInfo li = new MobileLinkQualityInfo(); + + li.setNetworkType(mNetworkInfo.getType()); + + mSamplingDataTracker.setCommonLinkQualityInfoFields(li); + + if (mNetworkInfo.getSubtype() != TelephonyManager.NETWORK_TYPE_UNKNOWN) { + li.setMobileNetworkType(mNetworkInfo.getSubtype()); + + NetworkDataEntry entry = getNetworkDataEntry(mNetworkInfo.getSubtype()); + if (entry != null) { + li.setTheoreticalRxBandwidth(entry.downloadBandwidth); + li.setTheoreticalRxBandwidth(entry.uploadBandwidth); + li.setTheoreticalLatency(entry.latency); + } + + if (mSignalStrength != null) { + li.setNormalizedSignalStrength(getNormalizedSignalStrength( + li.getMobileNetworkType(), mSignalStrength)); + } + } + + SignalStrength ss = mSignalStrength; + if (ss != null) { + + li.setRssi(ss.getGsmSignalStrength()); + li.setGsmErrorRate(ss.getGsmBitErrorRate()); + li.setCdmaDbm(ss.getCdmaDbm()); + li.setCdmaEcio(ss.getCdmaEcio()); + li.setEvdoDbm(ss.getEvdoDbm()); + li.setEvdoEcio(ss.getEvdoEcio()); + li.setEvdoSnr(ss.getEvdoSnr()); + li.setLteSignalStrength(ss.getLteSignalStrength()); + li.setLteRsrp(ss.getLteRsrp()); + li.setLteRsrq(ss.getLteRsrq()); + li.setLteRssnr(ss.getLteRssnr()); + li.setLteCqi(ss.getLteCqi()); + } + + if (VDBG) { + Slog.d(TAG, "Returning LinkQualityInfo with" + + " MobileNetworkType = " + String.valueOf(li.getMobileNetworkType()) + + " Theoretical Rx BW = " + String.valueOf(li.getTheoreticalRxBandwidth()) + + " gsm Signal Strength = " + String.valueOf(li.getRssi()) + + " cdma Signal Strength = " + String.valueOf(li.getCdmaDbm()) + + " evdo Signal Strength = " + String.valueOf(li.getEvdoDbm()) + + " Lte Signal Strength = " + String.valueOf(li.getLteSignalStrength())); + } + + return li; + } + + static class NetworkDataEntry { + public int networkType; + public int downloadBandwidth; // in kbps + public int uploadBandwidth; // in kbps + public int latency; // in millisecond + + NetworkDataEntry(int i1, int i2, int i3, int i4) { + networkType = i1; + downloadBandwidth = i2; + uploadBandwidth = i3; + latency = i4; + } + } + + private static NetworkDataEntry [] mTheoreticalBWTable = new NetworkDataEntry[] { + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EDGE, 237, 118, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_GPRS, 48, 40, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_UMTS, 384, 64, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSDPA, 14400, UNKNOWN, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSUPA, 14400, 5760, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPA, 14400, 5760, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPAP, 21000, 5760, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_CDMA, UNKNOWN, UNKNOWN, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_1xRTT, UNKNOWN, UNKNOWN, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_0, 2468, 153, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_A, 3072, 1800, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_B, 14700, 1800, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_IDEN, UNKNOWN, UNKNOWN, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_LTE, 100000, 50000, UNKNOWN), + new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EHRPD, UNKNOWN, UNKNOWN, UNKNOWN), + }; + + private static NetworkDataEntry getNetworkDataEntry(int networkType) { + for (NetworkDataEntry entry : mTheoreticalBWTable) { + if (entry.networkType == networkType) { + return entry; + } + } + + Slog.e(TAG, "Could not find Theoretical BW entry for " + String.valueOf(networkType)); + return null; + } + + private static int getNormalizedSignalStrength(int networkType, SignalStrength ss) { + + int level; + + switch(networkType) { + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSPAP: + level = ss.getGsmLevel(); + break; + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_1xRTT: + level = ss.getCdmaLevel(); + break; + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + level = ss.getEvdoLevel(); + break; + case TelephonyManager.NETWORK_TYPE_LTE: + level = ss.getLteLevel(); + break; + case TelephonyManager.NETWORK_TYPE_IDEN: + case TelephonyManager.NETWORK_TYPE_EHRPD: + default: + return UNKNOWN; + } + + return (level * LinkQualityInfo.NORMALIZED_SIGNAL_STRENGTH_RANGE) / + SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + } + + @Override + public void startSampling(SamplingDataTracker.SamplingSnapshot s) { + mSamplingDataTracker.startSampling(s); + } + + @Override + public void stopSampling(SamplingDataTracker.SamplingSnapshot s) { + mSamplingDataTracker.stopSampling(s); + } } diff --git a/core/java/android/net/MobileLinkQualityInfo.java b/core/java/android/net/MobileLinkQualityInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..a01fc8006a133a5f37fbeb9fbb1f60fe87ffbeb4 --- /dev/null +++ b/core/java/android/net/MobileLinkQualityInfo.java @@ -0,0 +1,286 @@ +/* + * 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.net; + +import android.os.Parcel; + +/** + * Class that represents useful attributes of mobile network links + * such as the upload/download throughput or error rate etc. + * @hide + */ +public class MobileLinkQualityInfo extends LinkQualityInfo { + // Represents TelephonyManager.NetworkType + private int mMobileNetworkType = UNKNOWN_INT; + private int mRssi = UNKNOWN_INT; + private int mGsmErrorRate = UNKNOWN_INT; + private int mCdmaDbm = UNKNOWN_INT; + private int mCdmaEcio = UNKNOWN_INT; + private int mEvdoDbm = UNKNOWN_INT; + private int mEvdoEcio = UNKNOWN_INT; + private int mEvdoSnr = UNKNOWN_INT; + private int mLteSignalStrength = UNKNOWN_INT; + private int mLteRsrp = UNKNOWN_INT; + private int mLteRsrq = UNKNOWN_INT; + private int mLteRssnr = UNKNOWN_INT; + private int mLteCqi = UNKNOWN_INT; + + /** + * Implement the Parcelable interface. + * @hide + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags, OBJECT_TYPE_MOBILE_LINK_QUALITY_INFO); + + dest.writeInt(mMobileNetworkType); + dest.writeInt(mRssi); + dest.writeInt(mGsmErrorRate); + dest.writeInt(mCdmaDbm); + dest.writeInt(mCdmaEcio); + dest.writeInt(mEvdoDbm); + dest.writeInt(mEvdoEcio); + dest.writeInt(mEvdoSnr); + dest.writeInt(mLteSignalStrength); + dest.writeInt(mLteRsrp); + dest.writeInt(mLteRsrq); + dest.writeInt(mLteRssnr); + dest.writeInt(mLteCqi); + } + + /* Un-parceling helper */ + /** + * @hide + */ + public static MobileLinkQualityInfo createFromParcelBody(Parcel in) { + + MobileLinkQualityInfo li = new MobileLinkQualityInfo(); + + li.initializeFromParcel(in); + + li.mMobileNetworkType = in.readInt(); + li.mRssi = in.readInt(); + li.mGsmErrorRate = in.readInt(); + li.mCdmaDbm = in.readInt(); + li.mCdmaEcio = in.readInt(); + li.mEvdoDbm = in.readInt(); + li.mEvdoEcio = in.readInt(); + li.mEvdoSnr = in.readInt(); + li.mLteSignalStrength = in.readInt(); + li.mLteRsrp = in.readInt(); + li.mLteRsrq = in.readInt(); + li.mLteRssnr = in.readInt(); + li.mLteCqi = in.readInt(); + + return li; + } + + /** + * returns mobile network type as defined by {@link android.telephony.TelephonyManager} + * @return network type or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getMobileNetworkType() { + return mMobileNetworkType; + } + + /** + * @hide + */ + public void setMobileNetworkType(int mobileNetworkType) { + mMobileNetworkType = mobileNetworkType; + } + + /** + * returns signal strength for GSM networks + * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getRssi() { + return mRssi; + } + + /** + * @hide + */ + public void setRssi(int Rssi) { + mRssi = Rssi; + } + + /** + * returns error rates for GSM networks + * @return error rate or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getGsmErrorRate() { + return mGsmErrorRate; + } + + /** + * @hide + */ + public void setGsmErrorRate(int gsmErrorRate) { + mGsmErrorRate = gsmErrorRate; + } + + /** + * returns signal strength for CDMA networks + * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getCdmaDbm() { + return mCdmaDbm; + } + + /** + * @hide + */ + public void setCdmaDbm(int cdmaDbm) { + mCdmaDbm = cdmaDbm; + } + + /** + * returns signal to noise ratio for CDMA networks + * @return signal to noise ratio in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getCdmaEcio() { + return mCdmaEcio; + } + + /** + * @hide + */ + public void setCdmaEcio(int cdmaEcio) { + mCdmaEcio = cdmaEcio; + } + + /** + * returns signal strength for EVDO networks + * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getEvdoDbm() { + return mEvdoDbm; + } + + /** + * @hide + */ + public void setEvdoDbm(int evdoDbm) { + mEvdoDbm = evdoDbm; + } + + /** + * returns signal to noise ratio for EVDO spectrum + * @return signal to noise ration in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getEvdoEcio() { + return mEvdoEcio; + } + + /** + * @hide + */ + public void setEvdoEcio(int evdoEcio) { + mEvdoEcio = evdoEcio; + } + + /** + * returns end-to-end signal to noise ratio for EVDO networks + * @return signal to noise ration in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getEvdoSnr() { + return mEvdoSnr; + } + + /** + * @hide + */ + public void setEvdoSnr(int evdoSnr) { + mEvdoSnr = evdoSnr; + } + + /** + * returns signal strength for LTE network + * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getLteSignalStrength() { + return mLteSignalStrength; + } + + /** + * @hide + */ + public void setLteSignalStrength(int lteSignalStrength) { + mLteSignalStrength = lteSignalStrength; + } + + /** + * returns RSRP (Reference Signal Received Power) for LTE network + * @return RSRP in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getLteRsrp() { + return mLteRsrp; + } + + /** + * @hide + */ + public void setLteRsrp(int lteRsrp) { + mLteRsrp = lteRsrp; + } + + /** + * returns RSRQ (Reference Signal Received Quality) for LTE network + * @return RSRQ ??? or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getLteRsrq() { + return mLteRsrq; + } + + /** + * @hide + */ + public void setLteRsrq(int lteRsrq) { + mLteRsrq = lteRsrq; + } + + /** + * returns signal to noise ratio for LTE networks + * @return signal to noise ration in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getLteRssnr() { + return mLteRssnr; + } + + /** + * @hide + */ + public void setLteRssnr(int lteRssnr) { + mLteRssnr = lteRssnr; + } + + /** + * returns channel quality indicator for LTE networks + * @return CQI or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getLteCqi() { + return mLteCqi; + } + + /** + * @hide + */ + public void setLteCqi(int lteCqi) { + mLteCqi = lteCqi; + } +} diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 689dae530112ec9e9082476954de9d539d2dc833..4d2a70ddf12bc5ed5f286869e5bb808682272a18 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -83,7 +83,7 @@ public class NetworkInfo implements Parcelable { /** Link has poor connectivity. */ VERIFYING_POOR_LINK, /** Checking if network is a captive portal */ - CAPTIVE_PORTAL_CHECK, + CAPTIVE_PORTAL_CHECK } /** @@ -120,6 +120,8 @@ public class NetworkInfo implements Parcelable { private String mExtraInfo; private boolean mIsFailover; private boolean mIsRoaming; + private boolean mIsConnectedToProvisioningNetwork; + /** * Indicates whether network connectivity is possible: */ @@ -148,6 +150,7 @@ public class NetworkInfo implements Parcelable { mState = State.UNKNOWN; mIsAvailable = false; // until we're told otherwise, assume unavailable mIsRoaming = false; + mIsConnectedToProvisioningNetwork = false; } /** {@hide} */ @@ -164,6 +167,7 @@ public class NetworkInfo implements Parcelable { mIsFailover = source.mIsFailover; mIsRoaming = source.mIsRoaming; mIsAvailable = source.mIsAvailable; + mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork; } } @@ -322,6 +326,22 @@ public class NetworkInfo implements Parcelable { } } + /** {@hide} */ + @VisibleForTesting + public boolean isConnectedToProvisioningNetwork() { + synchronized (this) { + return mIsConnectedToProvisioningNetwork; + } + } + + /** {@hide} */ + @VisibleForTesting + public void setIsConnectedToProvisioningNetwork(boolean val) { + synchronized (this) { + mIsConnectedToProvisioningNetwork = val; + } + } + /** * Reports the current coarse-grained state of the network. * @return the coarse-grained state @@ -405,7 +425,9 @@ public class NetworkInfo implements Parcelable { append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo). append(", roaming: ").append(mIsRoaming). append(", failover: ").append(mIsFailover). - append(", isAvailable: ").append(mIsAvailable); + append(", isAvailable: ").append(mIsAvailable). + append(", isConnectedToProvisioningNetwork: "). + append(mIsConnectedToProvisioningNetwork); return builder.toString(); } } @@ -433,6 +455,7 @@ public class NetworkInfo implements Parcelable { dest.writeInt(mIsFailover ? 1 : 0); dest.writeInt(mIsAvailable ? 1 : 0); dest.writeInt(mIsRoaming ? 1 : 0); + dest.writeInt(mIsConnectedToProvisioningNetwork ? 1 : 0); dest.writeString(mReason); dest.writeString(mExtraInfo); } @@ -455,6 +478,7 @@ public class NetworkInfo implements Parcelable { netInfo.mIsFailover = in.readInt() != 0; netInfo.mIsAvailable = in.readInt() != 0; netInfo.mIsRoaming = in.readInt() != 0; + netInfo.mIsConnectedToProvisioningNetwork = in.readInt() != 0; netInfo.mReason = in.readString(); netInfo.mExtraInfo = in.readString(); return netInfo; diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java index cf77a1cdbe6177042d71f426b1a47237ef644ec9..1ca925579fbb8848fb0e2e91cafdffb741c49802 100644 --- a/core/java/android/net/NetworkStateTracker.java +++ b/core/java/android/net/NetworkStateTracker.java @@ -83,7 +83,6 @@ public interface NetworkStateTracker { */ public static final int EVENT_NETWORK_DISCONNECTED = BASE_NETWORK_STATE_TRACKER + 5; - /** * ------------------------------------------------------------- * Control Interface @@ -119,6 +118,12 @@ public interface NetworkStateTracker { */ public LinkCapabilities getLinkCapabilities(); + /** + * Get interesting information about this network link + * @return a copy of link information, null if not available + */ + public LinkQualityInfo getLinkQualityInfo(); + /** * Return the system properties name associated with the tcp buffer sizes * for this network. @@ -143,6 +148,11 @@ public interface NetworkStateTracker { */ public void captivePortalCheckComplete(); + /** + * Captive portal check has completed + */ + public void captivePortalCheckCompleted(boolean isCaptive); + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} @@ -229,4 +239,20 @@ public interface NetworkStateTracker { * the underlying network specific code. */ public void supplyMessenger(Messenger messenger); + + /* + * Network interface name that we'll lookup for sampling data + */ + public String getNetworkInterfaceName(); + + /* + * Save the starting sample + */ + public void startSampling(SamplingDataTracker.SamplingSnapshot s); + + /* + * Save the ending sample + */ + public void stopSampling(SamplingDataTracker.SamplingSnapshot s); + } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 4ab479e239428c31e7e3a24fb82f27cf8d84be72..b24d3969f760df4996c649ec92d0add96efba16c 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -21,6 +21,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.UnknownHostException; import java.util.Collection; +import java.util.Locale; import android.util.Log; @@ -102,6 +103,11 @@ public class NetworkUtils { */ public native static String getDhcpError(); + /** + * Set the SO_MARK of {@code socketfd} to {@code mark} + */ + public native static void markSocket(int socketfd, int mark); + /** * Convert a IPv4 address from an integer to an InetAddress. * @param hostAddress an int corresponding to the IPv4 address in network byte order @@ -223,7 +229,7 @@ public class NetworkUtils { public static InetAddress hexToInet6Address(String addrHexString) throws IllegalArgumentException { try { - return numericToInetAddress(String.format("%s:%s:%s:%s:%s:%s:%s:%s", + return numericToInetAddress(String.format(Locale.US, "%s:%s:%s:%s:%s:%s:%s:%s", addrHexString.substring(0,4), addrHexString.substring(4,8), addrHexString.substring(8,12), addrHexString.substring(12,16), addrHexString.substring(16,20), addrHexString.substring(20,24), diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java new file mode 100644 index 0000000000000000000000000000000000000000..b674324197ed5c945affa360dbf233217c9f1e98 --- /dev/null +++ b/core/java/android/net/PacProxySelector.java @@ -0,0 +1,114 @@ +/* + * 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.net; + +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.net.IProxyService; +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; + +/** + * @hide + */ +public class PacProxySelector extends ProxySelector { + private static final String TAG = "PacProxySelector"; + public static final String PROXY_SERVICE = "com.android.net.IProxyService"; + private IProxyService mProxyService; + private final List mDefaultList; + + public PacProxySelector() { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + if (mProxyService == null) { + // Added because of b10267814 where mako is restarting. + Log.e(TAG, "PacManager: no proxy service"); + } + mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY); + } + + @Override + public List select(URI uri) { + if (mProxyService == null) { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + } + if (mProxyService == null) { + Log.e(TAG, "select: no proxy service return NO_PROXY"); + return Lists.newArrayList(java.net.Proxy.NO_PROXY); + } + String response = null; + String urlString; + try { + urlString = uri.toURL().toString(); + } catch (MalformedURLException e) { + urlString = uri.getHost(); + } + try { + response = mProxyService.resolvePacFile(uri.getHost(), urlString); + } catch (RemoteException e) { + e.printStackTrace(); + } + if (response == null) { + return mDefaultList; + } + + return parseResponse(response); + } + + private static List parseResponse(String response) { + String[] split = response.split(";"); + List ret = Lists.newArrayList(); + for (String s : split) { + String trimmed = s.trim(); + if (trimmed.equals("DIRECT")) { + ret.add(java.net.Proxy.NO_PROXY); + } else if (trimmed.startsWith("PROXY ")) { + String[] hostPort = trimmed.substring(6).split(":"); + String host = hostPort[0]; + int port; + try { + port = Integer.parseInt(hostPort[1]); + } catch (Exception e) { + port = 8080; + } + ret.add(new Proxy(Type.HTTP, new InetSocketAddress(host, port))); + } + } + if (ret.size() == 0) { + ret.add(java.net.Proxy.NO_PROXY); + } + return ret; + } + + @Override + public void connectFailed(URI uri, SocketAddress address, IOException failure) { + + } + +} diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index a408ea0c5fef5d4ef225fed358606a839a28b5d4..c3e14381f3af99659db17b2067cd06be24aa6fd0 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -18,38 +18,25 @@ package android.net; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.content.ContentResolver; import android.content.Context; -import android.database.ContentObserver; -import android.net.ProxyProperties; -import android.os.Handler; -import android.os.SystemProperties; import android.text.TextUtils; -import android.provider.Settings; import android.util.Log; -import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.protocol.HttpContext; + import java.net.InetSocketAddress; import java.net.ProxySelector; -import java.net.SocketAddress; import java.net.URI; -import java.net.UnknownHostException; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import junit.framework.Assert; - -import org.apache.http.conn.routing.HttpRoute; -import org.apache.http.conn.routing.HttpRoutePlanner; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; -import org.apache.http.protocol.HttpContext; - /** * A convenience class for accessing the user and default proxy * settings. @@ -60,6 +47,8 @@ public final class Proxy { private static final boolean DEBUG = false; private static final String TAG = "Proxy"; + private static final ProxySelector sDefaultProxySelector; + /** * Used to notify an app that's caching the default connection proxy * that either the default connection or its proxy has changed. @@ -96,6 +85,7 @@ public final class Proxy { static { HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); + sDefaultProxySelector = ProxySelector.getDefault(); } /** @@ -325,16 +315,19 @@ public final class Proxy { String host = null; String port = null; String exclList = null; + String pacFileUrl = null; if (p != null) { host = p.getHost(); port = Integer.toString(p.getPort()); exclList = p.getExclusionList(); + pacFileUrl = p.getPacFileUrl(); } - setHttpProxySystemProperty(host, port, exclList); + setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } /** @hide */ - public static final void setHttpProxySystemProperty(String host, String port, String exclList) { + public static final void setHttpProxySystemProperty(String host, String port, String exclList, + String pacFileUrl) { if (exclList != null) exclList = exclList.replace(",", "|"); if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); if (host != null) { @@ -358,5 +351,10 @@ public final class Proxy { System.clearProperty("http.nonProxyHosts"); System.clearProperty("https.nonProxyHosts"); } + if (!TextUtils.isEmpty(pacFileUrl)) { + ProxySelector.setDefault(new PacProxySelector()); + } else { + ProxySelector.setDefault(sDefaultProxySelector); + } } } diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index a4157c9225be0531ff6f59080f1be5d6f74d1585..44cfa94d5cd186dd543aeb4380462f3fa80772cd 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -20,9 +20,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Log; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Locale; @@ -38,17 +36,38 @@ public class ProxyProperties implements Parcelable { private String mExclusionList; private String[] mParsedExclusionList; + private String mPacFileUrl; + public static final String LOCAL_EXCL_LIST = ""; + public static final int LOCAL_PORT = -1; + public static final String LOCAL_HOST = "localhost"; + public ProxyProperties(String host, int port, String exclList) { mHost = host; mPort = port; setExclusionList(exclList); } + public ProxyProperties(String pacFileUrl) { + mHost = LOCAL_HOST; + mPort = LOCAL_PORT; + setExclusionList(LOCAL_EXCL_LIST); + mPacFileUrl = pacFileUrl; + } + + // Only used in PacManager after Local Proxy is bound. + public ProxyProperties(String pacFileUrl, int localProxyPort) { + mHost = LOCAL_HOST; + mPort = localProxyPort; + setExclusionList(LOCAL_EXCL_LIST); + mPacFileUrl = pacFileUrl; + } + private ProxyProperties(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; mExclusionList = exclList; mParsedExclusionList = parsedExclList; + mPacFileUrl = null; } // copy constructor instead of clone @@ -56,6 +75,7 @@ public class ProxyProperties implements Parcelable { if (source != null) { mHost = source.getHost(); mPort = source.getPort(); + mPacFileUrl = source.getPacFileUrl(); mExclusionList = source.getExclusionList(); mParsedExclusionList = source.mParsedExclusionList; } @@ -69,6 +89,10 @@ public class ProxyProperties implements Parcelable { return inetSocketAddress; } + public String getPacFileUrl() { + return mPacFileUrl; + } + public String getHost() { return mHost; } @@ -140,7 +164,10 @@ public class ProxyProperties implements Parcelable { @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (mHost != null) { + if (mPacFileUrl != null) { + sb.append("PAC Script: "); + sb.append(mPacFileUrl); + } else if (mHost != null) { sb.append("["); sb.append(mHost); sb.append("] "); @@ -158,6 +185,14 @@ public class ProxyProperties implements Parcelable { public boolean equals(Object o) { if (!(o instanceof ProxyProperties)) return false; ProxyProperties p = (ProxyProperties)o; + // If PAC URL is present in either then they must be equal. + // Other parameters will only be for fall back. + if (!TextUtils.isEmpty(mPacFileUrl)) { + return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort; + } + if (!TextUtils.isEmpty(p.getPacFileUrl())) { + return false; + } if (mExclusionList != null && !mExclusionList.equals(p.getExclusionList())) return false; if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) { return false; @@ -191,6 +226,14 @@ public class ProxyProperties implements Parcelable { * @hide */ public void writeToParcel(Parcel dest, int flags) { + if (mPacFileUrl != null) { + dest.writeByte((byte)1); + dest.writeString(mPacFileUrl); + dest.writeInt(mPort); + return; + } else { + dest.writeByte((byte)0); + } if (mHost != null) { dest.writeByte((byte)1); dest.writeString(mHost); @@ -211,7 +254,12 @@ public class ProxyProperties implements Parcelable { public ProxyProperties createFromParcel(Parcel in) { String host = null; int port = 0; - if (in.readByte() == 1) { + if (in.readByte() != 0) { + String url = in.readString(); + int localPort = in.readInt(); + return new ProxyProperties(url, localPort); + } + if (in.readByte() != 0) { host = in.readString(); port = in.readInt(); } diff --git a/core/java/android/net/SamplingDataTracker.java b/core/java/android/net/SamplingDataTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..acd56f2dfdad37e29747b618c3f594ae132f8d17 --- /dev/null +++ b/core/java/android/net/SamplingDataTracker.java @@ -0,0 +1,300 @@ +/* + * 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.net; + + +import android.os.SystemClock; +import android.util.Slog; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; + +/** + * @hide + */ +public class SamplingDataTracker +{ + private static final boolean DBG = false; + private static final String TAG = "SamplingDataTracker"; + + public static class SamplingSnapshot + { + public long mTxByteCount; + public long mRxByteCount; + public long mTxPacketCount; + public long mRxPacketCount; + public long mTxPacketErrorCount; + public long mRxPacketErrorCount; + public long mTimestamp; + } + + public static void getSamplingSnapshots(Map mapIfaceToSample) { + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader("/proc/net/dev")); + + // Skip over the line bearing column titles (there are 2 lines) + String line; + reader.readLine(); + reader.readLine(); + + while ((line = reader.readLine()) != null) { + + // remove leading whitespace + line = line.trim(); + + String[] tokens = line.split("[ ]+"); + if (tokens.length < 17) { + continue; + } + + /* column format is + * Interface (Recv)bytes packets errs drop fifo frame compressed multicast \ + * (Transmit)bytes packets errs drop fifo colls carrier compress + */ + + String currentIface = tokens[0].split(":")[0]; + if (DBG) Slog.d(TAG, "Found data for interface " + currentIface); + if (mapIfaceToSample.containsKey(currentIface)) { + + 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 + } + } + } + + if (DBG) { + Iterator it = mapIfaceToSample.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry kvpair = (Map.Entry)it.next(); + if (kvpair.getValue() == null) { + Slog.d(TAG, "could not find snapshot for interface " + kvpair.getKey()); + } + } + } + } catch(FileNotFoundException e) { + Slog.e(TAG, "could not find /proc/net/dev"); + } catch (IOException e) { + Slog.e(TAG, "could not read /proc/net/dev"); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + Slog.e(TAG, "could not close /proc/net/dev"); + } + } + } + + // Snapshots from previous sampling interval + private SamplingSnapshot mBeginningSample; + private SamplingSnapshot mEndingSample; + + // Starting snapshot of current interval + private SamplingSnapshot mLastSample; + + // Protects sampling data from concurrent access + public final Object mSamplingDataLock = new Object(); + + // We need long enough time for a good sample + private final int MINIMUM_SAMPLING_INTERVAL = 15 * 1000; + + // statistics is useless unless we have enough data + private final int MINIMUM_SAMPLED_PACKETS = 30; + + public void startSampling(SamplingSnapshot s) { + synchronized(mSamplingDataLock) { + mLastSample = s; + } + } + + public void stopSampling(SamplingSnapshot s) { + synchronized(mSamplingDataLock) { + if (mLastSample != null) { + if (s.mTimestamp - mLastSample.mTimestamp > MINIMUM_SAMPLING_INTERVAL + && getSampledPacketCount(mLastSample, s) > MINIMUM_SAMPLED_PACKETS) { + mBeginningSample = mLastSample; + mEndingSample = s; + mLastSample = null; + } else { + if (DBG) Slog.d(TAG, "Throwing current sample away because it is too small"); + } + } + } + } + + public void resetSamplingData() { + if (DBG) Slog.d(TAG, "Resetting sampled network data"); + synchronized(mSamplingDataLock) { + + // We could just take another sample here and treat it as an + // 'ending sample' effectively shortening sampling interval, but that + // requires extra work (specifically, reading the sample needs to be + // done asynchronously) + + mLastSample = null; + } + } + + public long getSampledTxByteCount() { + synchronized(mSamplingDataLock) { + if (mBeginningSample != null && mEndingSample != null) { + return mEndingSample.mTxByteCount - mBeginningSample.mTxByteCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + } + + public long getSampledTxPacketCount() { + synchronized(mSamplingDataLock) { + if (mBeginningSample != null && mEndingSample != null) { + return mEndingSample.mTxPacketCount - mBeginningSample.mTxPacketCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + } + + public long getSampledTxPacketErrorCount() { + synchronized(mSamplingDataLock) { + if (mBeginningSample != null && mEndingSample != null) { + return mEndingSample.mTxPacketErrorCount - mBeginningSample.mTxPacketErrorCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + } + + public long getSampledRxByteCount() { + synchronized(mSamplingDataLock) { + if (mBeginningSample != null && mEndingSample != null) { + return mEndingSample.mRxByteCount - mBeginningSample.mRxByteCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + } + + public long getSampledRxPacketCount() { + synchronized(mSamplingDataLock) { + if (mBeginningSample != null && mEndingSample != null) { + return mEndingSample.mRxPacketCount - mBeginningSample.mRxPacketCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + } + + public long getSampledPacketCount() { + return getSampledPacketCount(mBeginningSample, mEndingSample); + } + + public long getSampledPacketCount(SamplingSnapshot begin, SamplingSnapshot end) { + if (begin != null && end != null) { + long rxPacketCount = end.mRxPacketCount - begin.mRxPacketCount; + long txPacketCount = end.mTxPacketCount - begin.mTxPacketCount; + return rxPacketCount + txPacketCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + + public long getSampledPacketErrorCount() { + if (mBeginningSample != null && mEndingSample != null) { + long rxPacketErrorCount = getSampledRxPacketErrorCount(); + long txPacketErrorCount = getSampledTxPacketErrorCount(); + return rxPacketErrorCount + txPacketErrorCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + + public long getSampledRxPacketErrorCount() { + synchronized(mSamplingDataLock) { + if (mBeginningSample != null && mEndingSample != null) { + return mEndingSample.mRxPacketErrorCount - mBeginningSample.mRxPacketErrorCount; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + } + + public long getSampleTimestamp() { + synchronized(mSamplingDataLock) { + if (mEndingSample != null) { + return mEndingSample.mTimestamp; + } else { + return LinkQualityInfo.UNKNOWN_LONG; + } + } + } + + public int getSampleDuration() { + synchronized(mSamplingDataLock) { + if (mBeginningSample != null && mEndingSample != null) { + return (int) (mEndingSample.mTimestamp - mBeginningSample.mTimestamp); + } else { + return LinkQualityInfo.UNKNOWN_INT; + } + } + } + + public void setCommonLinkQualityInfoFields(LinkQualityInfo li) { + synchronized(mSamplingDataLock) { + li.setLastDataSampleTime(getSampleTimestamp()); + li.setDataSampleDuration(getSampleDuration()); + li.setPacketCount(getSampledPacketCount()); + li.setPacketErrorCount(getSampledPacketErrorCount()); + } + } +} + diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 132173dfac5c187483bdc70804d0a8916da39d52..a7a8a0a8f32b7ca1cd4ba82524a4bc3f8ede6efc 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -19,7 +19,6 @@ package android.net; import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; -import android.os.Environment.UserEnvironment; import android.os.StrictMode; import android.util.Log; import java.io.File; diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 65d3f2b27673f5f929c5d49846ab54b583bbf3da..d7dc7f5eac11a1a16332a314bd1924bf32a9b886 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -17,8 +17,8 @@ package android.net; import android.app.Activity; -import android.app.Service; import android.app.PendingIntent; +import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -30,12 +30,13 @@ import android.os.ServiceManager; import com.android.internal.net.VpnConfig; -import java.net.InetAddress; +import java.net.DatagramSocket; import java.net.Inet4Address; import java.net.Inet6Address; -import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; +import java.util.List; /** * VpnService is a base class for applications to extend and build their @@ -253,8 +254,8 @@ public class VpnService extends Service { public class Builder { private final VpnConfig mConfig = new VpnConfig(); - private final StringBuilder mAddresses = new StringBuilder(); - private final StringBuilder mRoutes = new StringBuilder(); + private final List mAddresses = new ArrayList(); + private final List mRoutes = new ArrayList(); public Builder() { mConfig.user = VpnService.this.getClass().getName(); @@ -328,8 +329,7 @@ public class VpnService extends Service { if (address.isAnyLocalAddress()) { throw new IllegalArgumentException("Bad address"); } - - mAddresses.append(' ' + address.getHostAddress() + '/' + prefixLength); + mAddresses.add(new LinkAddress(address, prefixLength)); return this; } @@ -363,8 +363,7 @@ public class VpnService extends Service { } } } - - mRoutes.append(String.format(" %s/%d", address.getHostAddress(), prefixLength)); + mRoutes.add(new RouteInfo(new LinkAddress(address, prefixLength), null)); return this; } @@ -465,8 +464,8 @@ public class VpnService extends Service { * @see VpnService */ public ParcelFileDescriptor establish() { - mConfig.addresses = mAddresses.toString(); - mConfig.routes = mRoutes.toString(); + mConfig.addresses = mAddresses; + mConfig.routes = mRoutes; try { return getService().establishVpn(mConfig); diff --git a/core/java/android/net/WifiLinkQualityInfo.java b/core/java/android/net/WifiLinkQualityInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..20ec9a739b5023e5399da841aaa401efd81e1fce --- /dev/null +++ b/core/java/android/net/WifiLinkQualityInfo.java @@ -0,0 +1,149 @@ +/* + * 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.net; + +import android.os.Parcel; + +/** + * Class that represents useful attributes of wifi network links + * such as the upload/download throughput or error rate etc. + * @hide + */ +public class WifiLinkQualityInfo extends LinkQualityInfo { + + /* Indicates Wifi network type such as b/g etc*/ + private int mType = UNKNOWN_INT; + + private String mBssid; + + /* Rssi found by scans */ + private int mRssi = UNKNOWN_INT; + + /* packet statistics */ + private long mTxGood = UNKNOWN_LONG; + private long mTxBad = UNKNOWN_LONG; + + /** + * Implement the Parcelable interface. + * @hide + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags, OBJECT_TYPE_WIFI_LINK_QUALITY_INFO); + + dest.writeInt(mType); + dest.writeInt(mRssi); + dest.writeLong(mTxGood); + dest.writeLong(mTxBad); + + dest.writeString(mBssid); + } + + /* Un-parceling helper */ + /** + * @hide + */ + public static WifiLinkQualityInfo createFromParcelBody(Parcel in) { + WifiLinkQualityInfo li = new WifiLinkQualityInfo(); + + li.initializeFromParcel(in); + + li.mType = in.readInt(); + li.mRssi = in.readInt(); + li.mTxGood = in.readLong(); + li.mTxBad = in.readLong(); + + li.mBssid = in.readString(); + + return li; + } + + /** + * returns Wifi network type + * @return network type or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getType() { + return mType; + } + + /** + * @hide + */ + public void setType(int type) { + mType = type; + } + + /** + * returns BSSID of the access point + * @return the BSSID, in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX} or null + */ + public String getBssid() { + return mBssid; + } + + /** + * @hide + */ + public void setBssid(String bssid) { + mBssid = bssid; + } + + /** + * returns RSSI of the network in raw form + * @return un-normalized RSSI or {@link android.net.LinkQualityInfo#UNKNOWN_INT} + */ + public int getRssi() { + return mRssi; + } + + /** + * @hide + */ + public void setRssi(int rssi) { + mRssi = rssi; + } + + /** + * returns number of packets transmitted without error + * @return number of packets or {@link android.net.LinkQualityInfo#UNKNOWN_LONG} + */ + public long getTxGood() { + return mTxGood; + } + + /** + * @hide + */ + public void setTxGood(long txGood) { + mTxGood = txGood; + } + + /** + * returns number of transmitted packets that encountered errors + * @return number of packets or {@link android.net.LinkQualityInfo#UNKNOWN_LONG} + */ + public long getTxBad() { + return mTxBad; + } + + /** + * @hide + */ + public void setTxBad(long txBad) { + mTxBad = txBad; + } +} diff --git a/core/java/android/nfc/IAppCallback.aidl b/core/java/android/nfc/IAppCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..95993088bb49514e572c5ea899f8a89457eb1156 --- /dev/null +++ b/core/java/android/nfc/IAppCallback.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 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; + +import android.nfc.BeamShareData; +import android.nfc.Tag; + +/** + * @hide + */ +interface IAppCallback +{ + BeamShareData createBeamShareData(); + void onNdefPushComplete(); + void onTagDiscovered(in Tag tag); +} diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl deleted file mode 100644 index 16771dc38dbe4c2f1cc69b82b68926a43627157c..0000000000000000000000000000000000000000 --- a/core/java/android/nfc/INdefPushCallback.aidl +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2011 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; - -import android.nfc.BeamShareData; - -/** - * @hide - */ -interface INdefPushCallback -{ - BeamShareData createBeamShareData(); - void onNdefPushComplete(); -} diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 39810ba9073949c2d172567ab5819dfd60236ec8..8414738cfa44efac27f2614a191d20121baf663c 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -21,9 +21,11 @@ import android.content.IntentFilter; import android.nfc.NdefMessage; import android.nfc.Tag; import android.nfc.TechListParcel; -import android.nfc.INdefPushCallback; +import android.nfc.IAppCallback; import android.nfc.INfcAdapterExtras; import android.nfc.INfcTag; +import android.nfc.INfcCardEmulation; +import android.os.Bundle; /** * @hide @@ -31,6 +33,7 @@ import android.nfc.INfcTag; interface INfcAdapter { INfcTag getNfcTagInterface(); + INfcCardEmulation getNfcCardEmulationInterface(); INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg); int getState(); @@ -42,9 +45,10 @@ interface INfcAdapter void setForegroundDispatch(in PendingIntent intent, in IntentFilter[] filters, in TechListParcel techLists); - void setNdefPushCallback(in INdefPushCallback callback); + void setAppCallback(in IAppCallback callback); void dispatch(in Tag tag); + void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras); void setP2pModes(int initatorModes, int targetModes); } diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b8a5ba7f226de73ec8708438b01c1400bc938e4e --- /dev/null +++ b/core/java/android/nfc/INfcCardEmulation.aidl @@ -0,0 +1,33 @@ +/* + * 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; + +import android.content.ComponentName; +import android.nfc.cardemulation.ApduServiceInfo; +import android.os.RemoteCallback; + +/** + * @hide + */ +interface INfcCardEmulation +{ + boolean isDefaultServiceForCategory(int userHandle, in ComponentName service, String category); + boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid); + boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category); + boolean setDefaultForNextTap(int userHandle, in ComponentName service); + List getServices(int userHandle, in String category); +} diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index 10183c087c3a26da4569de90bd7bc4fa3f29acdb..77c0234991388ae2348050947da071c0fcec200b 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -19,6 +19,8 @@ package android.nfc; import android.app.Activity; import android.app.Application; import android.net.Uri; +import android.nfc.NfcAdapter.ReaderCallback; +import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; @@ -35,7 +37,7 @@ import java.util.List; * * @hide */ -public final class NfcActivityManager extends INdefPushCallback.Stub +public final class NfcActivityManager extends IAppCallback.Stub implements Application.ActivityLifecycleCallbacks { static final String TAG = NfcAdapter.TAG; static final Boolean DBG = false; @@ -111,6 +113,11 @@ public final class NfcActivityManager extends INdefPushCallback.Stub NfcAdapter.CreateBeamUrisCallback uriCallback = null; Uri[] uris = null; int flags = 0; + int readerModeFlags = 0; + NfcAdapter.ReaderCallback readerCallback = null; + Bundle readerModeExtras = null; + Binder token; + public NfcActivityState(Activity activity) { if (activity.getWindow().isDestroyed()) { throw new IllegalStateException("activity is already destroyed"); @@ -120,6 +127,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 +139,8 @@ public final class NfcActivityManager extends INdefPushCallback.Stub onNdefPushCompleteCallback = null; uriCallback = null; uris = null; + readerModeFlags = 0; + token = null; } @Override public String toString() { @@ -190,6 +200,49 @@ public final class NfcActivityManager extends INdefPushCallback.Stub mDefaultEvent = new NfcEvent(mAdapter); } + public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, + Bundle extras) { + boolean isResumed; + Binder token; + synchronized (NfcActivityManager.this) { + NfcActivityState state = getActivityState(activity); + state.readerCallback = callback; + state.readerModeFlags = flags; + state.readerModeExtras = extras; + token = state.token; + isResumed = state.resumed; + } + if (isResumed) { + setReaderMode(token, flags, extras); + } + } + + public void disableReaderMode(Activity activity) { + boolean isResumed; + Binder token; + synchronized (NfcActivityManager.this) { + NfcActivityState state = getActivityState(activity); + state.readerCallback = null; + state.readerModeFlags = 0; + state.readerModeExtras = null; + token = state.token; + isResumed = state.resumed; + } + if (isResumed) { + setReaderMode(token, 0, null); + } + + } + + public void setReaderMode(Binder token, int flags, Bundle extras) { + if (DBG) Log.d(TAG, "Setting reader mode"); + try { + NfcAdapter.sService.setReaderMode(token, this, flags, extras); + } catch (RemoteException e) { + mAdapter.attemptDeadServiceRecovery(e); + } + } + public void setNdefPushContentUri(Activity activity, Uri[] uris) { boolean isResumed; synchronized (NfcActivityManager.this) { @@ -257,12 +310,12 @@ public final class NfcActivityManager extends INdefPushCallback.Stub } /** - * Request or unrequest NFC service callbacks for NDEF push. + * Request or unrequest NFC service callbacks. * Makes IPC call - do not hold lock. */ void requestNfcServiceCallback() { try { - NfcAdapter.sService.setNdefPushCallback(this); + NfcAdapter.sService.setAppCallback(this); } catch (RemoteException e) { mAdapter.attemptDeadServiceRecovery(e); } @@ -330,6 +383,22 @@ public final class NfcActivityManager extends INdefPushCallback.Stub } } + @Override + public void onTagDiscovered(Tag tag) throws RemoteException { + NfcAdapter.ReaderCallback callback; + synchronized (NfcActivityManager.this) { + NfcActivityState state = findResumedActivityState(); + if (state == null) return; + + callback = state.readerCallback; + } + + // Make callback without lock + if (callback != null) { + callback.onTagDiscovered(tag); + } + + } /** Callback from Activity life-cycle, on main thread */ @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } @@ -341,11 +410,20 @@ public final class NfcActivityManager extends INdefPushCallback.Stub /** Callback from Activity life-cycle, on main thread */ @Override public void onActivityResumed(Activity activity) { + int readerModeFlags = 0; + Bundle readerModeExtras = null; + 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; + readerModeExtras = state.readerModeExtras; + } + if (readerModeFlags != 0) { + setReaderMode(token, readerModeFlags, readerModeExtras); } requestNfcServiceCallback(); } @@ -353,11 +431,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, null); } } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index ca4a7d61ffd3697ecea485206b5c33bd63e2656a..486e75a5ca30cb62a04c8e4d0eb8208a2fbdde1c 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -33,6 +33,7 @@ import android.nfc.tech.MifareClassic; import android.nfc.tech.Ndef; import android.nfc.tech.NfcA; import android.nfc.tech.NfcF; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -195,6 +196,67 @@ 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, ReaderCallback, int, Bundle)}. + *

    + * 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, ReaderCallback, int, Bundle)}. + *

    + * 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, ReaderCallback, int, Bundle)}. + *

    + * 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, ReaderCallback, int, Bundle)}. + *

    + * 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, ReaderCallback, int, Bundle)}. + *

    + * Setting this flag enables polling for NfcBarcode technology. + */ + public static final int FLAG_READER_NFC_BARCODE = 0x10; + + /** + * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}. + *

    + * 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; + + /** + * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}. + *

    + * Setting this flag allows the caller to prevent the + * platform from playing sounds when it discovers a tag. + */ + public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 0x100; + + /** + * Int Extra for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}. + *

    + * Setting this integer extra allows the calling application to specify + * the delay that the platform will use for performing presence checks + * on any discovered tag. + */ + public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence"; + /** @hide */ public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1; @@ -227,6 +289,7 @@ public final class NfcAdapter { // recovery static INfcAdapter sService; static INfcTag sTagService; + static INfcCardEmulation sCardEmulationService; /** * The NfcAdapter object for each application context. @@ -245,6 +308,14 @@ public final class NfcAdapter { final NfcActivityManager mNfcActivityManager; final Context mContext; + /** + * A callback to be invoked when the system has found a tag in + * reader mode. + */ + public interface ReaderCallback { + public void onTagDiscovered(Tag tag); + } + /** * A callback to be invoked when the system successfully delivers your {@link NdefMessage} * to another device. @@ -348,6 +419,13 @@ public final class NfcAdapter { throw new UnsupportedOperationException(); } + try { + sCardEmulationService = sService.getNfcCardEmulationInterface(); + } catch (RemoteException e) { + Log.e(TAG, "could not retrieve card emulation service"); + throw new UnsupportedOperationException(); + } + sIsInitialized = true; } if (context == null) { @@ -455,6 +533,15 @@ public final class NfcAdapter { return sTagService; } + /** + * Returns the binder interface to the card emulation service. + * @hide + */ + public INfcCardEmulation getCardEmulationService() { + isEnabled(); + return sCardEmulationService; + } + /** * NFC service dead - attempt best effort recovery * @hide @@ -477,6 +564,13 @@ public final class NfcAdapter { Log.e(TAG, "could not retrieve NFC tag service during service recovery"); // nothing more can be done now, sService is still stale, we'll hit // this recovery path again later + return; + } + + try { + sCardEmulationService = service.getNfcCardEmulationInterface(); + } catch (RemoteException ee) { + Log.e(TAG, "could not retrieve NFC card emulation service during service recovery"); } return; @@ -1087,6 +1181,43 @@ public final class NfcAdapter { } } + /** + * Limit the NFC controller to reader mode while this Activity is in the foreground. + * + *

    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. + * + *

    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. + * + *

    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 callback the callback to be called when a tag is discovered + * @param flags Flags indicating poll technologies and other optional parameters + * @param extras Additional extras for configuring reader mode. + */ + public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, + Bundle extras) { + mNfcActivityManager.enableReaderMode(activity, callback, flags, extras); + } + + /** + * 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. * diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a62fdd6a6c5c849fb732082c91734ed307107ecd --- /dev/null +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable ApduServiceInfo; diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..d7ef4bc4e97f85bb727d24acb595a8ae6e4381f4 --- /dev/null +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -0,0 +1,442 @@ +/* + * 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.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * @hide + */ +public final class ApduServiceInfo implements Parcelable { + static final String TAG = "ApduServiceInfo"; + + /** + * The service that implements this + */ + final ResolveInfo mService; + + /** + * Description of the service + */ + final String mDescription; + + /** + * Convenience AID list + */ + final ArrayList mAids; + + /** + * Whether this service represents AIDs running on the host CPU + */ + final boolean mOnHost; + + /** + * All AID groups this service handles + */ + final ArrayList mAidGroups; + + /** + * Convenience hashmap + */ + final HashMap mCategoryToGroup; + + /** + * Whether this service should only be started when the device is unlocked. + */ + final boolean mRequiresDeviceUnlock; + + /** + * The id of the service banner specified in XML. + */ + final int mBannerResourceId; + + /** + * @hide + */ + public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, + ArrayList aidGroups, boolean requiresUnlock, int bannerResource) { + this.mService = info; + this.mDescription = description; + this.mAidGroups = aidGroups; + this.mAids = new ArrayList(); + this.mCategoryToGroup = new HashMap(); + this.mOnHost = onHost; + this.mRequiresDeviceUnlock = requiresUnlock; + for (AidGroup aidGroup : aidGroups) { + this.mCategoryToGroup.put(aidGroup.category, aidGroup); + this.mAids.addAll(aidGroup.aids); + } + this.mBannerResourceId = bannerResource; + } + + public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) + throws XmlPullParserException, IOException { + ServiceInfo si = info.serviceInfo; + XmlResourceParser parser = null; + try { + if (onHost) { + parser = si.loadXmlMetaData(pm, HostApduService.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); + if (parser == null) { + throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA + + " meta-data"); + } + } + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + + String tagName = parser.getName(); + if (onHost && !"host-apdu-service".equals(tagName)) { + throw new XmlPullParserException( + "Meta-data does not start with tag"); + } else if (!onHost && !"offhost-apdu-service".equals(tagName)) { + throw new XmlPullParserException( + "Meta-data does not start with tag"); + } + + Resources res = pm.getResourcesForApplication(si.applicationInfo); + AttributeSet attrs = Xml.asAttributeSet(parser); + if (onHost) { + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.HostApduService); + mService = info; + mDescription = sa.getString( + com.android.internal.R.styleable.HostApduService_description); + mRequiresDeviceUnlock = sa.getBoolean( + com.android.internal.R.styleable.HostApduService_requireDeviceUnlock, + false); + mBannerResourceId = sa.getResourceId( + com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1); + sa.recycle(); + } 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; + mBannerResourceId = sa.getResourceId( + com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1); + sa.recycle(); + } + + mAidGroups = new ArrayList(); + mCategoryToGroup = new HashMap(); + mAids = new ArrayList(); + mOnHost = onHost; + final int depth = parser.getDepth(); + AidGroup currentGroup = null; + + // Parsed values for the current AID group + while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && eventType != XmlPullParser.END_DOCUMENT) { + tagName = parser.getName(); + if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) && + currentGroup == null) { + final TypedArray groupAttrs = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AidGroup); + // Get category of AID group + String groupDescription = groupAttrs.getString( + com.android.internal.R.styleable.AidGroup_description); + String groupCategory = groupAttrs.getString( + com.android.internal.R.styleable.AidGroup_category); + if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) { + groupCategory = CardEmulation.CATEGORY_OTHER; + } + currentGroup = mCategoryToGroup.get(groupCategory); + if (currentGroup != null) { + if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) { + Log.e(TAG, "Not allowing multiple aid-groups in the " + + groupCategory + " category"); + currentGroup = null; + } + } else { + currentGroup = new AidGroup(groupCategory, groupDescription); + } + groupAttrs.recycle(); + } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) && + currentGroup != null) { + if (currentGroup.aids.size() > 0) { + if (!mCategoryToGroup.containsKey(currentGroup.category)) { + mAidGroups.add(currentGroup); + mCategoryToGroup.put(currentGroup.category, currentGroup); + } + } else { + Log.e(TAG, "Not adding with empty or invalid AIDs"); + } + currentGroup = null; + } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) && + currentGroup != null) { + final TypedArray a = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AidFilter); + String aid = a.getString(com.android.internal.R.styleable.AidFilter_name). + toUpperCase(); + if (isValidAid(aid) && !currentGroup.aids.contains(aid)) { + currentGroup.aids.add(aid); + mAids.add(aid); + } else { + Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); + } + a.recycle(); + } + } + } catch (NameNotFoundException e) { + throw new XmlPullParserException("Unable to create context for: " + si.packageName); + } finally { + if (parser != null) parser.close(); + } + } + + public ComponentName getComponent() { + return new ComponentName(mService.serviceInfo.packageName, + mService.serviceInfo.name); + } + + public ArrayList getAids() { + return mAids; + } + + public ArrayList getAidGroups() { + return mAidGroups; + } + + public boolean hasCategory(String category) { + return mCategoryToGroup.containsKey(category); + } + + public boolean isOnHost() { + return mOnHost; + } + + public boolean requiresUnlock() { + return mRequiresDeviceUnlock; + } + + public String getDescription() { + return mDescription; + } + + public CharSequence loadLabel(PackageManager pm) { + return mService.loadLabel(pm); + } + + public Drawable loadIcon(PackageManager pm) { + return mService.loadIcon(pm); + } + + public Drawable loadBanner(PackageManager pm) { + Resources res; + try { + res = pm.getResourcesForApplication(mService.serviceInfo.packageName); + Drawable banner = res.getDrawable(mBannerResourceId); + return banner; + } catch (NotFoundException e) { + Log.e(TAG, "Could not load banner."); + return null; + } catch (NameNotFoundException e) { + Log.e(TAG, "Could not load banner."); + return null; + } + } + + static boolean isValidAid(String aid) { + if (aid == null) + return false; + + int aidLength = aid.length(); + if (aidLength == 0 || (aidLength % 2) != 0) { + Log.e(TAG, "AID " + aid + " is not correctly formatted."); + return false; + } + // Minimum AID length is 5 bytes, 10 hex chars + if (aidLength < 10) { + Log.e(TAG, "AID " + aid + " is shorter than 5 bytes."); + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder("ApduService: "); + out.append(getComponent()); + out.append(", description: " + mDescription); + out.append(", AID Groups: "); + for (AidGroup aidGroup : mAidGroups) { + out.append(aidGroup.toString()); + } + return out.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ApduServiceInfo)) return false; + ApduServiceInfo thatService = (ApduServiceInfo) o; + + return thatService.getComponent().equals(this.getComponent()); + } + + @Override + public int hashCode() { + return getComponent().hashCode(); + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mService.writeToParcel(dest, flags); + dest.writeString(mDescription); + dest.writeInt(mOnHost ? 1 : 0); + dest.writeInt(mAidGroups.size()); + if (mAidGroups.size() > 0) { + dest.writeTypedList(mAidGroups); + } + dest.writeInt(mRequiresDeviceUnlock ? 1 : 0); + dest.writeInt(mBannerResourceId); + }; + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ApduServiceInfo createFromParcel(Parcel source) { + ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); + String description = source.readString(); + boolean onHost = (source.readInt() != 0) ? true : false; + ArrayList aidGroups = new ArrayList(); + int numGroups = source.readInt(); + if (numGroups > 0) { + source.readTypedList(aidGroups, AidGroup.CREATOR); + } + boolean requiresUnlock = (source.readInt() != 0) ? true : false; + int bannerResource = source.readInt(); + return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock, bannerResource); + } + + @Override + public ApduServiceInfo[] newArray(int size) { + return new ApduServiceInfo[size]; + } + }; + + public static class AidGroup implements Parcelable { + final ArrayList aids; + final String category; + final String description; + + AidGroup(ArrayList aids, String category, String description) { + this.aids = aids; + this.category = category; + this.description = description; + } + + AidGroup(String category, String description) { + this.aids = new ArrayList(); + this.category = category; + this.description = description; + } + + public String getCategory() { + return category; + } + + public ArrayList getAids() { + return aids; + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder("Category: " + category + + ", description: " + description + ", AIDs:"); + for (String aid : aids) { + out.append(aid); + out.append(", "); + } + return out.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(category); + dest.writeString(description); + dest.writeInt(aids.size()); + if (aids.size() > 0) { + dest.writeStringList(aids); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public AidGroup createFromParcel(Parcel source) { + String category = source.readString(); + String description = source.readString(); + int listSize = source.readInt(); + ArrayList aidList = new ArrayList(); + if (listSize > 0) { + source.readStringList(aidList); + } + return new AidGroup(aidList, category, description); + } + + @Override + public AidGroup[] newArray(int size) { + return new AidGroup[size]; + } + }; + } +} diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java new file mode 100644 index 0000000000000000000000000000000000000000..58d9616c72c4b60163274e02bdc30bc3e93515b1 --- /dev/null +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -0,0 +1,344 @@ +/* + * 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; + +/** + * This class can be used to query the state of + * NFC card emulation services. + * + * For a general introduction into NFC card emulation, + * please read the + * NFC card emulation developer guide.

    + * + *

    Use of this class requires the + * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present + * on the device. + */ +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 service {@link ComponentName} object passed in as an + * extra for {@link #ACTION_CHANGE_DEFAULT}. + * + * @see #ACTION_CHANGE_DEFAULT + */ + public static final String EXTRA_SERVICE_COMPONENT = "component"; + + /** + * Category used for NFC payment services. + */ + public static final String CATEGORY_PAYMENT = "payment"; + + /** + * Category that can be used for all other card emulation + * services. + */ + public static final String CATEGORY_OTHER = "other"; + + /** + * Return value for {@link #getSelectionModeForCategory(String)}. + * + *

    In this mode, the user has set a default service for this + * category. + * + *

    When using ISO-DEP card emulation with {@link HostApduService} + * or {@link OffHostApduService}, if a remote NFC device selects + * any of the Application IDs (AIDs) + * that the default service has registered in this category, + * that service will automatically be bound to to handle + * the transaction. + */ + public static final int SELECTION_MODE_PREFER_DEFAULT = 0; + + /** + * Return value for {@link #getSelectionModeForCategory(String)}. + * + *

    In this mode, when using ISO-DEP card emulation with {@link HostApduService} + * or {@link OffHostApduService}, whenever an Application ID (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)}. + * + *

    In this mode, when using ISO-DEP card emulation with {@link HostApduService} + * or {@link OffHostApduService}, the user will only be asked to select a service + * if the Application ID (AID) selected by the reader has been registered by multiple + * services. If there is only one service that has registered for the AID, + * that service will be invoked directly. + */ + public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; + + static boolean sIsInitialized = false; + static HashMap sCardEmus = new HashMap(); + static INfcCardEmulation sService; + + final Context mContext; + + private CardEmulation(Context context, INfcCardEmulation service) { + mContext = context.getApplicationContext(); + sService = service; + } + + /** + * Helper to get an instance of this class. + * + * @param adapter A reference to an NfcAdapter object. + * @return + */ + 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. + * + *

    Note that if {@link #getSelectionModeForCategory(String)} + * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT}, + * this method will always return false. That is because in these + * selection modes a default can't be set at the category level. For categories where + * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or + * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use + * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service + * is the default for a specific AID. + * + * @param service The ComponentName of the service + * @param category The category + * @return whether service is currently the default service for the category. + * + *

    Requires the {@link android.Manifest.permission#NFC} permission. + */ + 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 whether the service is the default handler for the specified AID + * + *

    Requires the {@link android.Manifest.permission#NFC} permission. + */ + 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 service selection mode for the passed in category. + * Valid return values are: + *

    {@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default + * service for this category, which will be preferred. + *

    {@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked + * every time what service he would like to use in this category. + *

    {@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 the selection mode for the passed in category + */ + 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 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/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java new file mode 100644 index 0000000000000000000000000000000000000000..ad34e61db910c31dc4333033cf72a8d89bdcc558 --- /dev/null +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -0,0 +1,385 @@ +package android.nfc.cardemulation; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +/** + *

    HostApduService is a convenience {@link Service} class that can be + * extended to emulate an NFC card inside an Android + * service component. + * + *

    + *

    Developer Guide

    + * For a general introduction into the topic of card emulation, + * please read the + * NFC card emulation developer guide.

    + *
    + * + *

    NFC Protocols

    + *

    Cards emulated by this class are based on the NFC-Forum ISO-DEP + * protocol (based on ISO/IEC 14443-4) and support processing + * command Application Protocol Data Units (APDUs) as + * defined in the ISO/IEC 7816-4 specification. + * + *

    Service selection

    + *

    When a remote NFC device wants to talk to your + * service, it sends a so-called + * "SELECT AID" APDU as defined in the ISO/IEC 7816-4 specification. + * The AID is an application identifier defined in ISO/IEC 7816-4. + * + *

    The registration procedure for AIDs is defined in the + * ISO/IEC 7816-5 specification. If you don't want to register an + * AID, you are free to use AIDs in the proprietary range: + * bits 8-5 of the first byte must each be set to '1'. For example, + * "0xF00102030405" is a proprietary AID. If you do use proprietary + * AIDs, it is recommended to choose an AID of at least 6 bytes, + * to reduce the risk of collisions with other applications that + * might be using proprietary AIDs as well. + * + *

    AID groups

    + *

    In some cases, a service may need to register multiple AIDs + * to implement a certain application, and it needs to be sure + * that it is the default handler for all of these AIDs (as opposed + * to some AIDs in the group going to another service). + * + *

    An AID group is a list of AIDs that should be considered as + * belonging together by the OS. For all AIDs in an AID group, the + * OS will guarantee one of the following: + *

      + *
    • All AIDs in the group are routed to this service + *
    • No AIDs in the group are routed to this service + *
    + * In other words, there is no in-between state, where some AIDs + * in the group can be routed to this service, and some to another. + *

    AID groups and categories

    + *

    Each AID group can be associated with a category. This allows + * the Android OS to classify services, and it allows the user to + * set defaults at the category level instead of the AID level. + * + *

    You can use + * {@link CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String)} + * to determine if your service is the default handler for a category. + * + *

    In this version of the platform, the only known categories + * are {@link CardEmulation#CATEGORY_PAYMENT} and {@link CardEmulation#CATEGORY_OTHER}. + * AID groups without a category, or with a category that is not recognized + * by the current platform version, will automatically be + * grouped into the {@link CardEmulation#CATEGORY_OTHER} category. + *

    Service AID registration

    + *

    To tell the platform which AIDs groups + * are requested by this service, a {@link #SERVICE_META_DATA} + * entry must be included in the declaration of the service. An + * example of a HostApduService manifest declaration is shown below: + *

     <service android:name=".MyHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE">
    + *     <intent-filter>
    + *         <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
    + *     </intent-filter>
    + *     <meta-data android:name="android.nfc.cardemulation.host_apdu_ervice" android:resource="@xml/apduservice"/>
    + * </service>
    + * + * This meta-data tag points to an apduservice.xml file. + * An example of this file with a single AID group declaration is shown below: + *
    + * <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    + *           android:description="@string/servicedesc" android:requireDeviceUnlock="false">
    + *       <aid-group android:description="@string/aiddescription" android:category="other">
    + *           <aid-filter android:name="F0010203040506"/>
    + *           <aid-filter android:name="F0394148148100"/>
    + *       </aid-group>
    + * </host-apdu-service>
    + * 
    + * + *

    The {@link android.R.styleable#HostApduService <host-apdu-service>} is required + * to contain a + * {@link android.R.styleable#HostApduService_description <android:description>} + * attribute that contains a user-friendly description of the service that may be shown in UI. + * The + * {@link android.R.styleable#HostApduService_requireDeviceUnlock <requireDeviceUnlock>} + * attribute can be used to specify that the device must be unlocked before this service + * can be invoked to handle APDUs. + *

    The {@link android.R.styleable#HostApduService <host-apdu-service>} must + * contain one or more {@link android.R.styleable#AidGroup <aid-group>} tags. + * Each {@link android.R.styleable#AidGroup <aid-group>} must contain one or + * more {@link android.R.styleable#AidFilter <aid-filter>} tags, each of which + * contains a single AID. The AID must be specified in hexadecimal format, and contain + * an even number of characters. + *

    AID conflict resolution

    + * Multiple HostApduServices may be installed on a single device, and the same AID + * can be registered by more than one service. The Android platform resolves AID + * conflicts depending on which category an AID belongs to. Each category may + * have a different conflict resolution policy. For example, for some categories + * the user may be able to select a default service in the Android settings UI. + * For other categories, to policy may be to always ask the user which service + * is to be invoked in case of conflict. + * + * To query the conflict resolution policy for a certain category, see + * {@link CardEmulation#getSelectionModeForCategory(String)}. + * + *

    Data exchange

    + *

    Once the platform has resolved a "SELECT AID" command APDU to a specific + * service component, the "SELECT AID" command APDU and all subsequent + * command APDUs will be sent to that service through + * {@link #processCommandApdu(byte[], Bundle)}, until either: + *

      + *
    • The NFC link is broken
    • + *
    • A "SELECT AID" APDU is received which resolves to another service
    • + *
    + * These two scenarios are indicated by a call to {@link #onDeactivated(int)}. + * + *

    Use of this class requires the + * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present + * on the device. + * + */ +public abstract class HostApduService extends Service { + /** + * The {@link Intent} action that must be declared as handled by the 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"; + + /** + * Reason for {@link #onDeactivated(int)}. + * Indicates deactivation was due to the NFC link + * being lost. + */ + public static final int DEACTIVATION_LINK_LOSS = 0; + + /** + * Reason for {@link #onDeactivated(int)}. + * + *

    Indicates deactivation was due to a different AID + * being selected (which implicitly deselects the AID + * currently active on the logical channel). + * + *

    Note that this next AID may still be resolved to this + * service, in which case {@link #processCommandApdu(byte[], Bundle)} + * will be called again. + */ + public static final int DEACTIVATION_DESELECTED = 1; + + static final String TAG = "ApduService"; + + /** + * MSG_COMMAND_APDU is sent by NfcService when + * a 7816-4 command APDU has been received. + * + * @hide + */ + public static final int MSG_COMMAND_APDU = 0; + + /** + * MSG_RESPONSE_APDU is sent to NfcService to send + * a response APDU back to the remote device. + * + * @hide + */ + public static final int MSG_RESPONSE_APDU = 1; + + /** + * MSG_DEACTIVATED is sent by NfcService when + * the current session is finished; either because + * another AID was selected that resolved to + * another service, or because the NFC link + * was deactivated. + * + * @hide + */ + public static final int MSG_DEACTIVATED = 2; + + /** + * + * @hide + */ + public static final int MSG_UNHANDLED = 3; + + /** + * @hide + */ + public static final String KEY_DATA = "data"; + + /** + * Messenger interface to NfcService for sending responses. + * Only accessed on main thread by the message handler. + * + * @hide + */ + Messenger mNfcService = null; + + final Messenger mMessenger = new Messenger(new MsgHandler()); + + final class MsgHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_COMMAND_APDU: + Bundle dataBundle = msg.getData(); + if (dataBundle == null) { + return; + } + if (mNfcService == null) mNfcService = msg.replyTo; + + byte[] apdu = dataBundle.getByteArray(KEY_DATA); + if (apdu != null) { + byte[] responseApdu = processCommandApdu(apdu, null); + if (responseApdu != null) { + if (mNfcService == null) { + Log.e(TAG, "Response not sent; service was deactivated."); + return; + } + Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); + Bundle responseBundle = new Bundle(); + responseBundle.putByteArray(KEY_DATA, responseApdu); + responseMsg.setData(responseBundle); + responseMsg.replyTo = mMessenger; + try { + mNfcService.send(responseMsg); + } catch (RemoteException e) { + Log.e("TAG", "Response not sent; RemoteException calling into " + + "NfcService."); + } + } + } else { + Log.e(TAG, "Received MSG_COMMAND_APDU without data."); + } + break; + case MSG_RESPONSE_APDU: + if (mNfcService == null) { + Log.e(TAG, "Response not sent; service was deactivated."); + return; + } + try { + msg.replyTo = mMessenger; + mNfcService.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling into NfcService."); + } + break; + case MSG_DEACTIVATED: + // Make sure we won't call into NfcService again + mNfcService = null; + onDeactivated(msg.arg1); + break; + case MSG_UNHANDLED: + if (mNfcService == null) { + Log.e(TAG, "notifyUnhandled not sent; service was deactivated."); + return; + } + try { + msg.replyTo = mMessenger; + mNfcService.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling into NfcService."); + } + break; + default: + super.handleMessage(msg); + } + } + } + + @Override + public final IBinder onBind(Intent intent) { + return mMessenger.getBinder(); + } + + /** + * Sends a response APDU back to the remote device. + * + *

    Note: this method may be called from any thread and will not block. + * @param responseApdu A byte-array containing the reponse APDU. + */ + public final void sendResponseApdu(byte[] responseApdu) { + Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); + Bundle dataBundle = new Bundle(); + dataBundle.putByteArray(KEY_DATA, responseApdu); + responseMsg.setData(dataBundle); + try { + mMessenger.send(responseMsg); + } catch (RemoteException e) { + Log.e("TAG", "Local messenger has died."); + } + } + + /** + * Calling this method allows the service to tell the OS + * that it won't be able to complete this transaction - + * for example, because it requires data connectivity + * that is not present at that moment. + * + * The OS may use this indication to give the user a list + * of alternative applications that can handle the last + * AID that was selected. If the user would select an + * application from the list, that action by itself + * will not cause the default to be changed; the selected + * application will be invoked for the next tap only. + * + * If there are no other applications that can handle + * this transaction, the OS will show an error dialog + * indicating your service could not complete the + * transaction. + * + *

    Note: this method may be called anywhere between + * the first {@link #processCommandApdu(byte[], Bundle)} + * call and a {@link #onDeactivated(int)} call. + */ + public final void notifyUnhandled() { + Message unhandledMsg = Message.obtain(null, MSG_UNHANDLED); + try { + mMessenger.send(unhandledMsg); + } catch (RemoteException e) { + Log.e("TAG", "Local messenger has died."); + } + } + + + /** + *

    This method will be called when a command APDU has been received + * from a remote device. A response APDU can be provided directly + * by returning a byte-array in this method. Note that in general + * response APDUs must be sent as quickly as possible, given the fact + * that the user is likely holding his device over an NFC reader + * when this method is called. + * + *

    If there are multiple services that have registered for the same + * AIDs in their meta-data entry, you will only get called if the user has + * explicitly selected your service, either as a default or just for the next tap. + * + *

    This method is running on the main thread of your application. + * If you cannot return a response APDU immediately, return null + * and use the {@link #sendResponseApdu(byte[])} method later. + * + * @param commandApdu The APDU that was received from the remote device + * @param extras A bundle containing extra data. May be null. + * @return a byte-array containing the response APDU, or null if no + * response APDU can be sent at this point. + */ + public abstract byte[] processCommandApdu(byte[] commandApdu, Bundle extras); + + /** + * This method will be called in two possible scenarios: + *

  • The NFC link has been deactivated or lost + *
  • A different AID has been selected and was resolved to a different + * service component + * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED} + */ + public abstract void onDeactivated(int reason); +} diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java new file mode 100644 index 0000000000000000000000000000000000000000..0d01762ae8e26fef8c682f39cfb4bf7bad482679 --- /dev/null +++ b/core/java/android/nfc/cardemulation/OffHostApduService.java @@ -0,0 +1,154 @@ +package android.nfc.cardemulation; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.IBinder; + +/** + *

    OffHostApduService is a convenience {@link Service} class that can be + * extended to describe one or more NFC applications that are residing + * off-host, for example on an embedded secure element or a UICC. + * + *

    + *

    Developer Guide

    + * For a general introduction into the topic of card emulation, + * please read the + * NFC card emulation developer guide.

    + *
    + * + *

    NFC Protocols

    + *

    Off-host applications represented by this class are based on the NFC-Forum ISO-DEP + * protocol (based on ISO/IEC 14443-4) and support processing + * command Application Protocol Data Units (APDUs) as + * defined in the ISO/IEC 7816-4 specification. + * + *

    Service selection

    + *

    When a remote NFC device wants to talk to your + * off-host NFC application, it sends a so-called + * "SELECT AID" APDU as defined in the ISO/IEC 7816-4 specification. + * The AID is an application identifier defined in ISO/IEC 7816-4. + * + *

    The registration procedure for AIDs is defined in the + * ISO/IEC 7816-5 specification. If you don't want to register an + * AID, you are free to use AIDs in the proprietary range: + * bits 8-5 of the first byte must each be set to '1'. For example, + * "0xF00102030405" is a proprietary AID. If you do use proprietary + * AIDs, it is recommended to choose an AID of at least 6 bytes, + * to reduce the risk of collisions with other applications that + * might be using proprietary AIDs as well. + * + *

    AID groups

    + *

    In some cases, an off-host environment may need to register multiple AIDs + * to implement a certain application, and it needs to be sure + * that it is the default handler for all of these AIDs (as opposed + * to some AIDs in the group going to another service). + * + *

    An AID group is a list of AIDs that should be considered as + * belonging together by the OS. For all AIDs in an AID group, the + * OS will guarantee one of the following: + *

      + *
    • All AIDs in the group are routed to the off-host execution environment + *
    • No AIDs in the group are routed to the off-host execution environment + *
    + * In other words, there is no in-between state, where some AIDs + * in the group can be routed to this off-host execution environment, + * and some to another or a host-based {@link HostApduService}. + *

    AID groups and categories

    + *

    Each AID group can be associated with a category. This allows + * the Android OS to classify services, and it allows the user to + * set defaults at the category level instead of the AID level. + * + *

    You can use + * {@link CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String)} + * to determine if your off-host service is the default handler for a category. + * + *

    In this version of the platform, the only known categories + * are {@link CardEmulation#CATEGORY_PAYMENT} and {@link CardEmulation#CATEGORY_OTHER}. + * AID groups without a category, or with a category that is not recognized + * by the current platform version, will automatically be + * grouped into the {@link CardEmulation#CATEGORY_OTHER} category. + * + *

    Service AID registration

    + *

    To tell the platform which AIDs + * reside off-host and are managed by this service, a {@link #SERVICE_META_DATA} + * entry must be included in the declaration of the service. An + * example of a OffHostApduService manifest declaration is shown below: + *

     <service android:name=".MyOffHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE">
    + *     <intent-filter>
    + *         <action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/>
    + *     </intent-filter>
    + *     <meta-data android:name="android.nfc.cardemulation.off_host_apdu_ervice" android:resource="@xml/apduservice"/>
    + * </service>
    + * + * This meta-data tag points to an apduservice.xml file. + * An example of this file with a single AID group declaration is shown below: + *
    + * <offhost-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    + *           android:description="@string/servicedesc">
    + *       <aid-group android:description="@string/subscription" android:category="other">
    + *           <aid-filter android:name="F0010203040506"/>
    + *           <aid-filter android:name="F0394148148100"/>
    + *       </aid-group>
    + * </offhost-apdu-service>
    + * 
    + * + *

    The {@link android.R.styleable#OffHostApduService <offhost-apdu-service>} is required + * to contain a + * {@link android.R.styleable#OffHostApduService_description <android:description>} + * attribute that contains a user-friendly description of the service that may be shown in UI. + * + *

    The {@link android.R.styleable#OffHostApduService <offhost-apdu-service>} must + * contain one or more {@link android.R.styleable#AidGroup <aid-group>} tags. + * Each {@link android.R.styleable#AidGroup <aid-group>} must contain one or + * more {@link android.R.styleable#AidFilter <aid-filter>} tags, each of which + * contains a single AID. The AID must be specified in hexadecimal format, and contain + * an even number of characters. + * + *

    This registration will allow the service to be included + * as an option for being the default handler for categories. + * The Android OS will take care of correctly + * routing the AIDs to the off-host execution environment, + * based on which service the user has selected to be the handler for a certain category. + * + *

    The service may define additional actions outside of the + * Android namespace that provide further interaction with + * the off-host execution environment. + * + *

    Use of this class requires the + * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present + * on the device. + */ +public abstract class OffHostApduService extends Service { + /** + * The {@link Intent} action that must be declared as handled by the service. + */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "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.cardemulation.off_host_apdu_service"; + + /** + * The Android platform itself will not bind to this service, + * but merely uses its declaration to keep track of what AIDs + * the service is interested in. This information is then used + * to present the user with a list of applications that can handle + * an AID, as well as correctly route those AIDs either to the host (in case + * the user preferred a {@link HostApduService}), or to an off-host + * execution environment (in case the user preferred a {@link OffHostApduService}. + * + * Implementers may define additional actions outside of the + * Android namespace that allow further interactions with + * the off-host execution environment. Such implementations + * would need to override this method. + */ + public abstract IBinder onBind(Intent intent); +} diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/core/java/android/nfc/tech/NfcBarcode.java index 76627deb4e108ca7d57013c37b6ba0da23cbe489..8901f287c47a245d112feb79412cea2b7a0e36db 100644 --- a/core/java/android/nfc/tech/NfcBarcode.java +++ b/core/java/android/nfc/tech/NfcBarcode.java @@ -102,15 +102,21 @@ public final class NfcBarcode extends BasicTagTechnology { * *

    The following 12 bytes are payload:

      *
    • In case of a URL payload, the payload is encoded in US-ASCII, - * following the limitations defined in RF3987, - * {@see http://www.ietf.org/rfc/rfc3987.txt}
    • - *
    • In case of GS1 EPC daya, {@see http://www.gs1.org/gsmp/kc/epcglobal/tds/} - * for more details.
    + * following the limitations defined in RFC3987. + * {@see RFC 3987}
  • + *
  • In case of GS1 EPC data, see + * GS1 Electronic Product Code (EPC) Tag Data Standard (TDS) for more details. + *
  • + * *

    The last 2 bytes comprise the CRC. * *

    Does not cause any RF activity and does not block. * * @return a byte array containing the barcode + * @see + * Kovio 128-bit NFC barcode datasheet + * @see + * Kovio 128-bit NFC barcode data format */ public byte[] getBarcode() { switch (mType) { diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index ce5f1633a8cd541bb8c12af63e81282260d67d23..d4a30064707ee5ea415def9a79c1905054cb75de 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -177,8 +177,9 @@ import java.util.concurrent.atomic.AtomicInteger; public abstract class AsyncTask { private static final String LOG_TAG = "AsyncTask"; - private static final int CORE_POOL_SIZE = 5; - private static final int MAXIMUM_POOL_SIZE = 128; + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CORE_POOL_SIZE = CPU_COUNT + 1; + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { @@ -190,7 +191,7 @@ public abstract class AsyncTask { }; private static final BlockingQueue sPoolWorkQueue = - new LinkedBlockingQueue(10); + new LinkedBlockingQueue(128); /** * An {@link Executor} that can be used to execute tasks in parallel. diff --git a/core/java/android/os/BatteryProperties.aidl b/core/java/android/os/BatteryProperties.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31fa7b80181fd308060ab8c952b5f3c2a4576f96 --- /dev/null +++ b/core/java/android/os/BatteryProperties.aidl @@ -0,0 +1,19 @@ +/* +** Copyright 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.os; + +parcelable BatteryProperties; diff --git a/core/java/android/os/BatteryProperties.java b/core/java/android/os/BatteryProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..5df5214d733bd329c67aeb79a9ff9d076f5210d0 --- /dev/null +++ b/core/java/android/os/BatteryProperties.java @@ -0,0 +1,87 @@ +/* Copyright 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.os; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * {@hide} + */ +public class BatteryProperties implements Parcelable { + public boolean chargerAcOnline; + public boolean chargerUsbOnline; + public boolean chargerWirelessOnline; + public int batteryStatus; + public int batteryHealth; + public boolean batteryPresent; + public int batteryLevel; + public int batteryVoltage; + public int batteryCurrentNow; + public int batteryChargeCounter; + public int batteryTemperature; + public String batteryTechnology; + + /* + * Parcel read/write code must be kept in sync with + * frameworks/native/services/batteryservice/BatteryProperties.cpp + */ + + private BatteryProperties(Parcel p) { + chargerAcOnline = p.readInt() == 1 ? true : false; + chargerUsbOnline = p.readInt() == 1 ? true : false; + chargerWirelessOnline = p.readInt() == 1 ? true : false; + batteryStatus = p.readInt(); + batteryHealth = p.readInt(); + batteryPresent = p.readInt() == 1 ? true : false; + batteryLevel = p.readInt(); + batteryVoltage = p.readInt(); + batteryCurrentNow = p.readInt(); + batteryChargeCounter = p.readInt(); + batteryTemperature = p.readInt(); + batteryTechnology = p.readString(); + } + + public void writeToParcel(Parcel p, int flags) { + p.writeInt(chargerAcOnline ? 1 : 0); + p.writeInt(chargerUsbOnline ? 1 : 0); + p.writeInt(chargerWirelessOnline ? 1 : 0); + p.writeInt(batteryStatus); + p.writeInt(batteryHealth); + p.writeInt(batteryPresent ? 1 : 0); + p.writeInt(batteryLevel); + p.writeInt(batteryVoltage); + p.writeInt(batteryCurrentNow); + p.writeInt(batteryChargeCounter); + p.writeInt(batteryTemperature); + p.writeString(batteryTechnology); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public BatteryProperties createFromParcel(Parcel p) { + return new BatteryProperties(p); + } + + public BatteryProperties[] newArray(int size) { + return new BatteryProperties[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 8804066ebeea8e5c3ef9188b457feaae914b278a..bc98a0b504255b93db540c000c9bc9e4efca05e8 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -41,7 +41,10 @@ import android.util.TimeUtils; public abstract class BatteryStats implements Parcelable { private static final boolean LOCAL_LOGV = false; - + + /** @hide */ + public static final String SERVICE_NAME = "batterystats"; + /** * A constant indicating a partial wake lock timer. */ @@ -97,6 +100,11 @@ public abstract class BatteryStats implements Parcelable { */ public static final int VIBRATOR_ON = 9; + /** + * A constant indicating a foreground activity timer + */ + public static final int FOREGROUND_ACTIVITY = 10; + /** * Include all of the data in the stats, including previously saved data. */ @@ -125,7 +133,7 @@ public abstract class BatteryStats implements Parcelable { /** * Bump the version on this if the checkin format changes. */ - private static final int BATTERY_STATS_CHECKIN_VERSION = 5; + private static final int BATTERY_STATS_CHECKIN_VERSION = 7; private static final long BYTES_PER_KB = 1024; private static final long BYTES_PER_MB = 1048576; // 1024^2 @@ -137,6 +145,7 @@ public abstract class BatteryStats implements Parcelable { private static final String PROCESS_DATA = "pr"; private static final String SENSOR_DATA = "sr"; private static final String VIBRATOR_DATA = "vib"; + private static final String FOREGROUND_DATA = "fg"; private static final String WAKELOCK_DATA = "wl"; private static final String KERNEL_WAKELOCK_DATA = "kwl"; private static final String NETWORK_DATA = "nt"; @@ -146,6 +155,7 @@ public abstract class BatteryStats implements Parcelable { private static final String BATTERY_LEVEL_DATA = "lv"; private static final String WIFI_DATA = "wfl"; private static final String MISC_DATA = "m"; + private static final String HISTORY_DATA = "h"; private static final String SCREEN_BRIGHTNESS_DATA = "br"; private static final String SIGNAL_STRENGTH_TIME_DATA = "sgt"; private static final String SIGNAL_SCANNING_TIME_DATA = "sst"; @@ -253,17 +263,7 @@ public abstract class BatteryStats implements Parcelable { * {@hide} */ public abstract int getUid(); - - /** - * {@hide} - */ - public abstract long getTcpBytesReceived(int which); - - /** - * {@hide} - */ - public abstract long getTcpBytesSent(int which); - + public abstract void noteWifiRunningLocked(); public abstract void noteWifiStoppedLocked(); public abstract void noteFullWifiLockAcquiredLocked(); @@ -276,6 +276,8 @@ public abstract class BatteryStats implements Parcelable { public abstract void noteAudioTurnedOffLocked(); public abstract void noteVideoTurnedOnLocked(); public abstract void noteVideoTurnedOffLocked(); + public abstract void noteActivityResumedLocked(); + public abstract void noteActivityPausedLocked(); public abstract long getWifiRunningTime(long batteryRealtime, int which); public abstract long getFullWifiLockTime(long batteryRealtime, int which); public abstract long getWifiScanTime(long batteryRealtime, int which); @@ -283,6 +285,7 @@ public abstract class BatteryStats implements Parcelable { int which); public abstract long getAudioTurnedOnTime(long batteryRealtime, int which); public abstract long getVideoTurnedOnTime(long batteryRealtime, int which); + public abstract Timer getForegroundActivityTimer(); public abstract Timer getVibratorOnTimer(); /** @@ -295,11 +298,14 @@ public abstract class BatteryStats implements Parcelable { }; public static final int NUM_USER_ACTIVITY_TYPES = 3; - + public abstract void noteUserActivityLocked(int type); public abstract boolean hasUserActivity(); public abstract int getUserActivityCount(int type, int which); - + + public abstract boolean hasNetworkActivity(); + public abstract long getNetworkActivityCount(int type, int which); + public static abstract class Sensor { /* * FIXME: it's not correct to use this magic value because it @@ -920,6 +926,15 @@ public abstract class BatteryStats implements Parcelable { */ public abstract long getBluetoothOnTime(long batteryRealtime, int which); + public static final int NETWORK_MOBILE_RX_BYTES = 0; + public static final int NETWORK_MOBILE_TX_BYTES = 1; + public static final int NETWORK_WIFI_RX_BYTES = 2; + public static final int NETWORK_WIFI_TX_BYTES = 3; + + public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_BYTES + 1; + + public abstract long getNetworkActivityCount(int type, int which); + /** * Return whether we are currently running on battery. */ @@ -1194,13 +1209,13 @@ public abstract class BatteryStats implements Parcelable { pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); pw.print(uid); pw.print(','); pw.print(category); pw.print(','); - pw.print(type); + pw.print(type); for (Object arg : args) { - pw.print(','); - pw.print(arg); + pw.print(','); + pw.print(arg); } - pw.print('\n'); + pw.println(); } /** @@ -1229,7 +1244,7 @@ public abstract class BatteryStats implements Parcelable { final int NU = uidStats.size(); String category = STAT_NAMES[which]; - + // Dump "battery" stat dumpLine(pw, 0 /* uid */, category, BATTERY_DATA, which == STATS_SINCE_CHARGED ? getStartCount() : "N/A", @@ -1237,16 +1252,20 @@ public abstract class BatteryStats implements Parcelable { totalRealtime / 1000, totalUptime / 1000); // Calculate total network and wakelock times across all uids. - long rxTotal = 0; - long txTotal = 0; + long mobileRxTotal = 0; + long mobileTxTotal = 0; + long wifiRxTotal = 0; + long wifiTxTotal = 0; long fullWakeLockTimeTotal = 0; long partialWakeLockTimeTotal = 0; for (int iu = 0; iu < NU; iu++) { Uid u = uidStats.valueAt(iu); - rxTotal += u.getTcpBytesReceived(which); - txTotal += u.getTcpBytesSent(which); - + mobileRxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which); + mobileTxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which); + wifiRxTotal += u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which); + wifiTxTotal += u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which); + Map wakelocks = u.getWakelockStats(); if (wakelocks.size() > 0) { for (Map.Entry ent @@ -1270,7 +1289,8 @@ public abstract class BatteryStats implements Parcelable { // Dump misc stats dumpLine(pw, 0 /* uid */, category, MISC_DATA, screenOnTime / 1000, phoneOnTime / 1000, wifiOnTime / 1000, - wifiRunningTime / 1000, bluetoothOnTime / 1000, rxTotal, txTotal, + wifiRunningTime / 1000, bluetoothOnTime / 1000, + mobileRxTotal, mobileTxTotal, wifiRxTotal, wifiTxTotal, fullWakeLockTimeTotal, partialWakeLockTimeTotal, getInputEventCount(which)); @@ -1341,14 +1361,18 @@ public abstract class BatteryStats implements Parcelable { } Uid u = uidStats.valueAt(iu); // Dump Network stats per uid, if any - long rx = u.getTcpBytesReceived(which); - long tx = u.getTcpBytesSent(which); + long mobileRx = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which); + long mobileTx = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which); + long wifiRx = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which); + long wifiTx = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which); long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which); long wifiScanTime = u.getWifiScanTime(batteryRealtime, which); long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which); - - if (rx > 0 || tx > 0) dumpLine(pw, uid, category, NETWORK_DATA, rx, tx); - + + if (mobileRx > 0 || mobileTx > 0 || wifiRx > 0 || wifiTx > 0) { + dumpLine(pw, uid, category, NETWORK_DATA, mobileRx, mobileTx, wifiRx, wifiTx); + } + if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || uidWifiRunningTime != 0) { dumpLine(pw, uid, category, WIFI_DATA, @@ -1384,7 +1408,11 @@ public abstract class BatteryStats implements Parcelable { // Only log if we had at lease one wakelock... if (sb.length() > 0) { - dumpLine(pw, uid, category, WAKELOCK_DATA, ent.getKey(), sb.toString()); + String name = ent.getKey(); + if (name.indexOf(',') >= 0) { + name = name.replace(',', '_'); + } + dumpLine(pw, uid, category, WAKELOCK_DATA, name, sb.toString()); } } } @@ -1417,22 +1445,31 @@ public abstract class BatteryStats implements Parcelable { } } + Timer fgTimer = u.getForegroundActivityTimer(); + if (fgTimer != null) { + // Convert from microseconds to milliseconds with rounding + long totalTime = (fgTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000; + int count = fgTimer.getCountLocked(which); + if (totalTime != 0) { + dumpLine(pw, uid, category, FOREGROUND_DATA, totalTime, count); + } + } + Map processStats = u.getProcessStats(); if (processStats.size() > 0) { for (Map.Entry ent : processStats.entrySet()) { Uid.Proc ps = ent.getValue(); - - long userTime = ps.getUserTime(which); - long systemTime = ps.getSystemTime(which); - int starts = ps.getStarts(which); - - if (userTime != 0 || systemTime != 0 || starts != 0) { - dumpLine(pw, uid, category, PROCESS_DATA, - ent.getKey(), // proc - userTime * 10, // cpu time in ms - systemTime * 10, // user time in ms - starts); // process starts + + final long userMillis = ps.getUserTime(which) * 10; + final long systemMillis = ps.getSystemTime(which) * 10; + final long foregroundMillis = ps.getForegroundTime(which) * 10; + final long starts = ps.getStarts(which); + + if (userMillis != 0 || systemMillis != 0 || foregroundMillis != 0 + || starts != 0) { + dumpLine(pw, uid, category, PROCESS_DATA, ent.getKey(), userMillis, + systemMillis, foregroundMillis, starts); } } } @@ -1551,8 +1588,10 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); // Calculate total network and wakelock times across all uids. - long rxTotal = 0; - long txTotal = 0; + long mobileRxTotal = 0; + long mobileTxTotal = 0; + long wifiRxTotal = 0; + long wifiTxTotal = 0; long fullWakeLockTimeTotalMicros = 0; long partialWakeLockTimeTotalMicros = 0; @@ -1605,9 +1644,11 @@ public abstract class BatteryStats implements Parcelable { for (int iu = 0; iu < NU; iu++) { Uid u = uidStats.valueAt(iu); - rxTotal += u.getTcpBytesReceived(which); - txTotal += u.getTcpBytesSent(which); - + mobileRxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which); + mobileTxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which); + wifiRxTotal += u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which); + wifiTxTotal += u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which); + Map wakelocks = u.getWakelockStats(); if (wakelocks.size() > 0) { for (Map.Entry ent @@ -1640,8 +1681,11 @@ public abstract class BatteryStats implements Parcelable { } pw.print(prefix); - pw.print(" Total received: "); pw.print(formatBytesLocked(rxTotal)); - pw.print(", Total sent: "); pw.println(formatBytesLocked(txTotal)); + pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotal)); + pw.print(", Total sent: "); pw.println(formatBytesLocked(mobileTxTotal)); + pw.print(prefix); + pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotal)); + pw.print(", Total sent: "); pw.println(formatBytesLocked(wifiTxTotal)); sb.setLength(0); sb.append(prefix); sb.append(" Total full wakelock time: "); formatTimeMs(sb, @@ -1760,8 +1804,8 @@ public abstract class BatteryStats implements Parcelable { for (int i=0; i 0 || mobileTxBytes > 0) { + pw.print(prefix); pw.print(" Mobile network: "); + pw.print(formatBytesLocked(mobileRxBytes)); pw.print(" received, "); + pw.print(formatBytesLocked(mobileTxBytes)); pw.println(" sent"); } - + if (wifiRxBytes > 0 || wifiTxBytes > 0) { + pw.print(prefix); pw.print(" Wi-Fi network: "); + pw.print(formatBytesLocked(wifiRxBytes)); pw.print(" received, "); + pw.print(formatBytesLocked(wifiTxBytes)); pw.println(" sent"); + } + if (u.hasUserActivity()) { boolean hasData = false; for (int i=0; i processStats = u.getProcessStats(); if (processStats.size() > 0) { for (Map.Entry ent @@ -1968,23 +2040,26 @@ public abstract class BatteryStats implements Parcelable { Uid.Proc ps = ent.getValue(); long userTime; long systemTime; + long foregroundTime; int starts; int numExcessive; userTime = ps.getUserTime(which); systemTime = ps.getSystemTime(which); + foregroundTime = ps.getForegroundTime(which); starts = ps.getStarts(which); numExcessive = which == STATS_SINCE_CHARGED ? ps.countExcessivePowers() : 0; - if (userTime != 0 || systemTime != 0 || starts != 0 + if (userTime != 0 || systemTime != 0 || foregroundTime != 0 || starts != 0 || numExcessive != 0) { sb.setLength(0); sb.append(prefix); sb.append(" Proc "); sb.append(ent.getKey()); sb.append(":\n"); sb.append(prefix); sb.append(" CPU: "); formatTime(sb, userTime); sb.append("usr + "); - formatTime(sb, systemTime); sb.append("krn"); + formatTime(sb, systemTime); sb.append("krn ; "); + formatTime(sb, foregroundTime); sb.append("fg"); if (starts != 0) { sb.append("\n"); sb.append(prefix); sb.append(" "); sb.append(starts); sb.append(" proc starts"); @@ -2042,7 +2117,7 @@ public abstract class BatteryStats implements Parcelable { sb.append(sent.getKey()); sb.append(":\n"); sb.append(prefix); sb.append(" Created for: "); formatTimeMs(sb, startTime / 1000); - sb.append(" uptime\n"); + sb.append("uptime\n"); sb.append(prefix); sb.append(" Starts: "); sb.append(starts); sb.append(", launches: "); sb.append(launches); @@ -2207,6 +2282,30 @@ public abstract class BatteryStats implements Parcelable { } oldState = rec.states; } + + public void printNextItemCheckin(PrintWriter pw, HistoryItem rec, long now) { + pw.print(rec.time-now); + pw.print(","); + if (rec.cmd == HistoryItem.CMD_START) { + pw.print("start"); + } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) { + pw.print("overflow"); + } else { + pw.print(rec.batteryLevel); + pw.print(","); + pw.print(rec.states); + pw.print(","); + pw.print(rec.batteryStatus); + pw.print(","); + pw.print(rec.batteryHealth); + pw.print(","); + pw.print(rec.batteryPlugType); + pw.print(","); + pw.print((int)rec.batteryTemperature); + pw.print(","); + pw.print((int)rec.batteryVoltage); + } + } } /** @@ -2215,7 +2314,7 @@ public abstract class BatteryStats implements Parcelable { * @param pw a Printer to receive the dump output. */ @SuppressWarnings("unused") - public void dumpLocked(PrintWriter pw) { + public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) { prepareForDumpLocked(); long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); @@ -2267,29 +2366,41 @@ public abstract class BatteryStats implements Parcelable { if (didPid) { pw.println(""); } - - pw.println("Statistics since last charge:"); - pw.println(" System starts: " + getStartCount() - + ", currently on battery: " + getIsOnBattery()); - dumpLocked(pw, "", STATS_SINCE_CHARGED, -1); - pw.println(""); + + if (!isUnpluggedOnly) { + pw.println("Statistics since last charge:"); + pw.println(" System starts: " + getStartCount() + + ", currently on battery: " + getIsOnBattery()); + dumpLocked(pw, "", STATS_SINCE_CHARGED, reqUid); + pw.println(""); + } pw.println("Statistics since last unplugged:"); - dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, -1); + dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, reqUid); } @SuppressWarnings("unused") - public void dumpCheckinLocked(PrintWriter pw, String[] args, List apps) { + public void dumpCheckinLocked( + PrintWriter pw, List apps, boolean isUnpluggedOnly, + boolean includeHistory) { prepareForDumpLocked(); - - boolean isUnpluggedOnly = false; - for (String arg : args) { - if ("-u".equals(arg)) { - if (LOCAL_LOGV) Log.v("BatteryStats", "Dumping unplugged data"); - isUnpluggedOnly = true; + long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); + + if (includeHistory) { + final HistoryItem rec = new HistoryItem(); + if (startIteratingHistoryLocked()) { + HistoryPrinter hprinter = new HistoryPrinter(); + while (getNextHistoryLocked(rec)) { + pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); + pw.print(0); pw.print(','); + pw.print(HISTORY_DATA); pw.print(','); + hprinter.printNextItemCheckin(pw, rec, now); + pw.println(); + } + finishIteratingHistoryLocked(); } } - + if (apps != null) { SparseArray> uids = new SparseArray>(); for (int i=0; iApplications targeting this or a later release will get these + * new changes in behavior:

    + *
      + *
    • The default result of {android.preference.PreferenceActivity#isValidFragment + * PreferenceActivity.isValueFragment} becomes false instead of true.
    • + *
    • In {@link android.webkit.WebView}, apps targeting earlier versions will have + * JS URLs evaluated directly and any result of the evaluation will not replace + * the current page content. Apps targetting KITKAT or later that load a JS URL will + * have the result of that URL replace the content of the current page
    • + *
    • {@link android.app.AlarmManager#set AlarmManager.set} becomes interpreted as + * an inexact value, to give the system more flexibility in scheduling alarms.
    • + *
    • {@link android.content.Context#getSharedPreferences(String, int) + * Context.getSharedPreferences} no longer allows a null name.
    • + *
    • {@link android.widget.RelativeLayout} changes to compute wrapped content + * margins correctly.
    • + *
    • {@link android.app.ActionBar}'s window content overlay is allowed to be + * drawn.
    • + *
    + */ + public static final int KITKAT = 19; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index ad54a141d795352e9287bd64fe7ff57a48e642bf..5de365f5335c012367637f9d8f3f8eec3fe95200 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -16,15 +16,13 @@ package android.os; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.List; import java.util.Set; /** @@ -32,18 +30,21 @@ import java.util.Set; * */ public final class Bundle implements Parcelable, Cloneable { - private static final String LOG_TAG = "Bundle"; + private static final String TAG = "Bundle"; + static final boolean DEBUG = false; public static final Bundle EMPTY; + static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' + static { EMPTY = new Bundle(); - EMPTY.mMap = Collections.unmodifiableMap(new HashMap()); + EMPTY.mMap = ArrayMap.EMPTY; } // Invariant - exactly one of mMap / mParcelledData will be null // (except inside a call to unparcel) - /* package */ Map mMap = null; + /* package */ ArrayMap mMap = null; /* * If mParcelledData is non-null, then mMap will be null and the @@ -65,7 +66,7 @@ public final class Bundle implements Parcelable, Cloneable { * Constructs a new, empty Bundle. */ public Bundle() { - mMap = new HashMap(); + mMap = new ArrayMap(); mClassLoader = getClass().getClassLoader(); } @@ -91,7 +92,7 @@ public final class Bundle implements Parcelable, Cloneable { * inside of the Bundle. */ public Bundle(ClassLoader loader) { - mMap = new HashMap(); + mMap = new ArrayMap(); mClassLoader = loader; } @@ -102,7 +103,7 @@ public final class Bundle implements Parcelable, Cloneable { * @param capacity the initial capacity of the Bundle */ public Bundle(int capacity) { - mMap = new HashMap(capacity); + mMap = new ArrayMap(capacity); mClassLoader = getClass().getClassLoader(); } @@ -122,7 +123,7 @@ public final class Bundle implements Parcelable, Cloneable { } if (b.mMap != null) { - mMap = new HashMap(b.mMap); + mMap = new ArrayMap(b.mMap); } else { mMap = null; } @@ -157,12 +158,12 @@ public final class Bundle implements Parcelable, Cloneable { unparcel(); int size = mMap.size(); if (size > 1) { - Log.w(LOG_TAG, "getPairValue() used on Bundle with multiple pairs."); + Log.w(TAG, "getPairValue() used on Bundle with multiple pairs."); } if (size == 0) { return null; } - Object o = mMap.values().iterator().next(); + Object o = mMap.valueAt(0); try { return (String) o; } catch (ClassCastException e) { @@ -210,19 +211,28 @@ public final class Bundle implements Parcelable, Cloneable { */ /* package */ synchronized void unparcel() { if (mParcelledData == null) { + if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + ": no parcelled data"); return; } int N = mParcelledData.readInt(); + if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + ": reading " + N + " maps"); if (N < 0) { return; } if (mMap == null) { - mMap = new HashMap(N); + mMap = new ArrayMap(N); + } else { + mMap.erase(); + mMap.ensureCapacity(N); } - mParcelledData.readMapInternal(mMap, N, mClassLoader); + mParcelledData.readArrayMapInternal(mMap, N, mClassLoader); mParcelledData.recycle(); mParcelledData = null; + if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + " final map: " + mMap); } /** @@ -331,9 +341,8 @@ public final class Bundle implements Parcelable, Cloneable { } } else { // It's been unparcelled, so we need to walk the map - Iterator> iter = mMap.entrySet().iterator(); - while (!fdFound && iter.hasNext()) { - Object obj = iter.next().getValue(); + for (int i=mMap.size()-1; i>=0; i--) { + Object obj = mMap.valueAt(i); if (obj instanceof Parcelable) { if ((((Parcelable)obj).describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) { @@ -546,6 +555,13 @@ public final class Bundle implements Parcelable, Cloneable { mFdsKnown = false; } + /** {@hide} */ + public void putParcelableList(String key, List 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 @@ -785,6 +801,8 @@ public final class Bundle implements Parcelable, Cloneable { */ public boolean getBoolean(String key) { unparcel(); + if (DEBUG) Log.d(TAG, "Getting boolean in " + + Integer.toHexString(System.identityHashCode(this))); return getBoolean(key, false); } @@ -801,8 +819,8 @@ public final class Bundle implements Parcelable, Cloneable { sb.append(". The default value "); sb.append(defaultValue); sb.append(" was returned."); - Log.w(LOG_TAG, sb.toString()); - Log.w(LOG_TAG, "Attempt to cast generated internal exception:", e); + Log.w(TAG, sb.toString()); + Log.w(TAG, "Attempt to cast generated internal exception:", e); } private void typeWarning(String key, Object value, String className, @@ -1636,21 +1654,22 @@ public final class Bundle implements Parcelable, Cloneable { if (mParcelledData != null) { int length = mParcelledData.dataSize(); parcel.writeInt(length); - parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L' + parcel.writeInt(BUNDLE_MAGIC); parcel.appendFrom(mParcelledData, 0, length); } else { + int lengthPos = parcel.dataPosition(); parcel.writeInt(-1); // dummy, will hold length - parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L' + parcel.writeInt(BUNDLE_MAGIC); - int oldPos = parcel.dataPosition(); - parcel.writeMapInternal(mMap); - int newPos = parcel.dataPosition(); + int startPos = parcel.dataPosition(); + parcel.writeArrayMapInternal(mMap); + int endPos = parcel.dataPosition(); // Backpatch length - parcel.setDataPosition(oldPos - 8); - int length = newPos - oldPos; + parcel.setDataPosition(lengthPos); + int length = endPos - startPos; parcel.writeInt(length); - parcel.setDataPosition(newPos); + parcel.setDataPosition(endPos); } } finally { parcel.restoreAllowFds(oldAllowFds); @@ -1672,11 +1691,10 @@ public final class Bundle implements Parcelable, Cloneable { void readFromParcelInner(Parcel parcel, int length) { int magic = parcel.readInt(); - if (magic != 0x4C444E42) { + if (magic != BUNDLE_MAGIC) { //noinspection ThrowableInstanceNeverThrown - String st = Log.getStackTraceString(new RuntimeException()); - Log.e("Bundle", "readBundle: bad magic number"); - Log.e("Bundle", "readBundle: trace = " + st); + throw new IllegalStateException("Bad magic number for Bundle: 0x" + + Integer.toHexString(magic)); } // Advance within this Parcel @@ -1686,8 +1704,10 @@ public final class Bundle implements Parcelable, Cloneable { Parcel p = Parcel.obtain(); p.setDataPosition(0); p.appendFrom(parcel, offset, length); + if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this)) + + ": " + length + " bundle bytes starting at " + offset); p.setDataPosition(0); - + mParcelledData = p; mHasFds = p.hasFileDescriptors(); mFdsKnown = true; diff --git a/core/java/android/os/CancellationSignal.java b/core/java/android/os/CancellationSignal.java index dcba9b7758dd077146c4b1c6c8aaf929a8c38c83..e8053d5d275d55eccbfd3577245a0fc10dae56e6 100644 --- a/core/java/android/os/CancellationSignal.java +++ b/core/java/android/os/CancellationSignal.java @@ -17,7 +17,6 @@ package android.os; import android.os.ICancellationSignal; -import android.os.ICancellationSignal.Stub; /** * Provides the ability to cancel an operation in progress. diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 989bd2c7533291e70f8e60f5cc02b467ee437e9d..974bf8ddfb80b3692c6014dcec85d1aff1fede77 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -16,6 +16,7 @@ package android.os; +import com.android.internal.util.FastPrintWriter; import com.android.internal.util.TypedProperties; import android.util.Log; @@ -108,31 +109,88 @@ public final class Debug * process. The returns info broken down by dalvik, native, and other. All results are in kB. */ public static class MemoryInfo implements Parcelable { - /** The proportional set size for dalvik. */ + /** The proportional set size for dalvik heap. (Doesn't include other Dalvik overhead.) */ public int dalvikPss; - /** The private dirty pages used by dalvik. */ + /** The proportional set size that is swappable for dalvik heap. */ + /** @hide We may want to expose this, eventually. */ + public int dalvikSwappablePss; + /** The private dirty pages used by dalvik heap. */ public int dalvikPrivateDirty; - /** The shared dirty pages used by dalvik. */ + /** The shared dirty pages used by dalvik heap. */ public int dalvikSharedDirty; + /** The private clean pages used by dalvik heap. */ + /** @hide We may want to expose this, eventually. */ + public int dalvikPrivateClean; + /** The shared clean pages used by dalvik heap. */ + /** @hide We may want to expose this, eventually. */ + public int dalvikSharedClean; + /** The dirty dalvik pages that have been swapped out. */ + /** @hide We may want to expose this, eventually. */ + public int dalvikSwappedOut; /** The proportional set size for the native heap. */ public int nativePss; + /** The proportional set size that is swappable for the native heap. */ + /** @hide We may want to expose this, eventually. */ + public int nativeSwappablePss; /** The private dirty pages used by the native heap. */ public int nativePrivateDirty; /** The shared dirty pages used by the native heap. */ public int nativeSharedDirty; + /** The private clean pages used by the native heap. */ + /** @hide We may want to expose this, eventually. */ + public int nativePrivateClean; + /** The shared clean pages used by the native heap. */ + /** @hide We may want to expose this, eventually. */ + public int nativeSharedClean; + /** The dirty native pages that have been swapped out. */ + /** @hide We may want to expose this, eventually. */ + public int nativeSwappedOut; /** The proportional set size for everything else. */ public int otherPss; + /** The proportional set size that is swappable for everything else. */ + /** @hide We may want to expose this, eventually. */ + public int otherSwappablePss; /** The private dirty pages used by everything else. */ public int otherPrivateDirty; /** The shared dirty pages used by everything else. */ public int otherSharedDirty; + /** The private clean pages used by everything else. */ + /** @hide We may want to expose this, eventually. */ + public int otherPrivateClean; + /** The shared clean pages used by everything else. */ + /** @hide We may want to expose this, eventually. */ + public int otherSharedClean; + /** The dirty pages used by anyting else that have been swapped out. */ + /** @hide We may want to expose this, eventually. */ + public int otherSwappedOut; /** @hide */ - public static final int NUM_OTHER_STATS = 10; + public static final int NUM_OTHER_STATS = 16; - private int[] otherStats = new int[NUM_OTHER_STATS*3]; + /** @hide */ + public static final int NUM_DVK_STATS = 5; + + /** @hide */ + public static final int NUM_CATEGORIES = 7; + + /** @hide */ + public static final int offsetPss = 0; + /** @hide */ + public static final int offsetSwappablePss = 1; + /** @hide */ + public static final int offsetPrivateDirty = 2; + /** @hide */ + public static final int offsetSharedDirty = 3; + /** @hide */ + public static final int offsetPrivateClean = 4; + /** @hide */ + public static final int offsetSharedClean = 5; + /** @hide */ + public static final int offsetSwappedOut = 6; + + private int[] otherStats = new int[(NUM_OTHER_STATS+NUM_DVK_STATS)*NUM_CATEGORIES]; public MemoryInfo() { } @@ -144,6 +202,22 @@ public final class Debug return dalvikPss + nativePss + otherPss; } + /** + * @hide Return total PSS memory usage in kB. + */ + public int getTotalUss() { + return dalvikPrivateClean + dalvikPrivateDirty + + nativePrivateClean + nativePrivateDirty + + otherPrivateClean + otherPrivateDirty; + } + + /** + * Return total PSS memory usage in kB. + */ + public int getTotalSwappablePss() { + return dalvikSwappablePss + nativeSwappablePss + otherSwappablePss; + } + /** * Return total private dirty memory usage in kB. */ @@ -158,35 +232,89 @@ public final class Debug return dalvikSharedDirty + nativeSharedDirty + otherSharedDirty; } - /* @hide */ + /** + * Return total shared clean memory usage in kB. + */ + public int getTotalPrivateClean() { + return dalvikPrivateClean + nativePrivateClean + otherPrivateClean; + } + + /** + * Return total shared clean memory usage in kB. + */ + public int getTotalSharedClean() { + return dalvikSharedClean + nativeSharedClean + otherSharedClean; + } + + /** + * Return total swapped out memory in kB. + * @hide + */ + public int getTotalSwappedOut() { + return dalvikSwappedOut + nativeSwappedOut + otherSwappedOut; + } + + /** @hide */ public int getOtherPss(int which) { - return otherStats[which*3]; + return otherStats[which*NUM_CATEGORIES + offsetPss]; + } + + + /** @hide */ + public int getOtherSwappablePss(int which) { + return otherStats[which*NUM_CATEGORIES + offsetSwappablePss]; } - /* @hide */ + + /** @hide */ public int getOtherPrivateDirty(int which) { - return otherStats[which*3 + 1]; + return otherStats[which*NUM_CATEGORIES + offsetPrivateDirty]; } - /* @hide */ + /** @hide */ public int getOtherSharedDirty(int which) { - return otherStats[which*3 + 2]; + return otherStats[which*NUM_CATEGORIES + offsetSharedDirty]; + } + + /** @hide */ + public int getOtherPrivateClean(int which) { + return otherStats[which*NUM_CATEGORIES + offsetPrivateClean]; } + /** @hide */ + public int getOtherSharedClean(int which) { + return otherStats[which*NUM_CATEGORIES + offsetSharedClean]; + } + + /** @hide */ + public int getOtherSwappedOut(int which) { + return otherStats[which*NUM_CATEGORIES + offsetSwappedOut]; + } - /* @hide */ + /** @hide */ public static String getOtherLabel(int which) { switch (which) { - case 0: return "Stack"; - case 1: return "Cursor"; - case 2: return "Ashmem"; - case 3: return "Other dev"; - case 4: return ".so mmap"; - case 5: return ".jar mmap"; - case 6: return ".apk mmap"; - case 7: return ".ttf mmap"; - case 8: return ".dex mmap"; - case 9: return "Other mmap"; + case 0: return "Dalvik Other"; + case 1: return "Stack"; + case 2: return "Cursor"; + case 3: return "Ashmem"; + case 4: return "Other dev"; + case 5: return ".so mmap"; + case 6: return ".jar mmap"; + case 7: return ".apk mmap"; + case 8: return ".ttf mmap"; + case 9: return ".dex mmap"; + case 10: return "code mmap"; + case 11: return "image mmap"; + case 12: return "Other mmap"; + case 13: return "Graphics"; + case 14: return "GL"; + case 15: return "Memtrack"; + case 16: return ".Heap"; + case 17: return ".LOS"; + case 18: return ".LinearAlloc"; + case 19: return ".GC"; + case 20: return ".JITCache"; default: return "????"; } } @@ -197,27 +325,51 @@ public final class Debug public void writeToParcel(Parcel dest, int flags) { dest.writeInt(dalvikPss); + dest.writeInt(dalvikSwappablePss); dest.writeInt(dalvikPrivateDirty); dest.writeInt(dalvikSharedDirty); + dest.writeInt(dalvikPrivateClean); + dest.writeInt(dalvikSharedClean); + dest.writeInt(dalvikSwappedOut); dest.writeInt(nativePss); + dest.writeInt(nativeSwappablePss); dest.writeInt(nativePrivateDirty); dest.writeInt(nativeSharedDirty); + dest.writeInt(nativePrivateClean); + dest.writeInt(nativeSharedClean); + dest.writeInt(nativeSwappedOut); dest.writeInt(otherPss); + dest.writeInt(otherSwappablePss); dest.writeInt(otherPrivateDirty); dest.writeInt(otherSharedDirty); + dest.writeInt(otherPrivateClean); + dest.writeInt(otherSharedClean); + dest.writeInt(otherSwappedOut); dest.writeIntArray(otherStats); } public void readFromParcel(Parcel source) { dalvikPss = source.readInt(); + dalvikSwappablePss = source.readInt(); dalvikPrivateDirty = source.readInt(); dalvikSharedDirty = source.readInt(); + dalvikPrivateClean = source.readInt(); + dalvikSharedClean = source.readInt(); + dalvikSwappedOut = source.readInt(); nativePss = source.readInt(); + nativeSwappablePss = source.readInt(); nativePrivateDirty = source.readInt(); nativeSharedDirty = source.readInt(); + nativePrivateClean = source.readInt(); + nativeSharedClean = source.readInt(); + nativeSwappedOut = source.readInt(); otherPss = source.readInt(); + otherSwappablePss = source.readInt(); otherPrivateDirty = source.readInt(); otherSharedDirty = source.readInt(); + otherPrivateClean = source.readInt(); + otherSharedClean = source.readInt(); + otherSwappedOut = source.readInt(); otherStats = source.createIntArray(); } @@ -364,7 +516,7 @@ public final class Debug PrintWriter outStream = null; try { FileOutputStream fos = new FileOutputStream(SYSFS_QEMU_TRACE_STATE); - outStream = new PrintWriter(new OutputStreamWriter(fos)); + outStream = new FastPrintWriter(fos); outStream.println("1"); } catch (Exception e) { } finally { @@ -392,7 +544,7 @@ public final class Debug PrintWriter outStream = null; try { FileOutputStream fos = new FileOutputStream(SYSFS_QEMU_TRACE_STATE); - outStream = new PrintWriter(new OutputStreamWriter(fos)); + outStream = new FastPrintWriter(fos); outStream.println("0"); } catch (Exception e) { // We could print an error message here but we probably want @@ -611,7 +763,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo /** * Clears the global size of objects allocated. - * @see #getGlobalAllocCountSize() + * @see #getGlobalAllocSize() */ public static void resetGlobalAllocSize() { VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES); @@ -891,9 +1043,38 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo /** * Retrieves the PSS memory used by the process as given by the - * smaps. @hide + * smaps. Optionally supply a long array of 1 entry to also + * receive the uss of the process. @hide + */ + public static native long getPss(int pid, long[] outUss); + + /** @hide */ + public static final int MEMINFO_TOTAL = 0; + /** @hide */ + public static final int MEMINFO_FREE = 1; + /** @hide */ + public static final int MEMINFO_BUFFERS = 2; + /** @hide */ + public static final int MEMINFO_CACHED = 3; + /** @hide */ + public static final int MEMINFO_SHMEM = 4; + /** @hide */ + public static final int MEMINFO_SLAB = 5; + /** @hide */ + public static final int MEMINFO_SWAP_TOTAL = 6; + /** @hide */ + public static final int MEMINFO_SWAP_FREE = 7; + /** @hide */ + public static final int MEMINFO_ZRAM_TOTAL = 8; + /** @hide */ + public static final int MEMINFO_COUNT = 9; + + /** + * Retrieves /proc/meminfo. outSizes is filled with fields + * as defined by MEMINFO_* offsets. + * @hide */ - public static native long getPss(int pid); + public static native void getMemInfo(long[] outSizes); /** * Establish an object allocation limit in the current thread. @@ -1400,7 +1581,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo /** * Return a String describing the calling method and location at a particular stack depth. - * @param callStack the Thread stack + * @param callStack the Thread stack * @param depth the depth of stack to return information for. * @return the String describing the caller at that depth. */ @@ -1428,6 +1609,22 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo return sb.toString(); } + /** + * Return a string consisting of methods and locations at multiple call stack levels. + * @param depth the number of levels to return, starting with the immediate caller. + * @return a string describing the call stack. + * {@hide} + */ + public static String getCallers(final int start, int depth) { + final StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); + StringBuffer sb = new StringBuffer(); + depth += start; + for (int i = start; i < depth; i++) { + sb.append(getCaller(callStack, i)).append(" "); + } + return sb.toString(); + } + /** * Like {@link #getCallers(int)}, but each location is append to the string * as a new line with linePrefix in front of it. diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 61eef1f169979746c92a176df6c260132ede4c6f..b5413db04b708a31f8b50ce7a0c2f1e727699f7b 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -16,6 +16,7 @@ package android.os; +import android.content.Context; import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; @@ -23,9 +24,11 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.google.android.collect.Lists; import java.io.File; import java.io.IOException; +import java.util.ArrayList; /** * Provides access to environment variables. @@ -37,10 +40,20 @@ public class Environment { private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE"; private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET"; private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE"; + private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE"; private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT"; /** {@hide} */ - public static String DIRECTORY_ANDROID = "Android"; + public static final String DIR_ANDROID = "Android"; + private static final String DIR_DATA = "data"; + private static final String DIR_MEDIA = "media"; + private static final String DIR_OBB = "obb"; + private static final String DIR_FILES = "files"; + private static final String DIR_CACHE = "cache"; + + /** {@hide} */ + @Deprecated + public static final String DIRECTORY_ANDROID = DIR_ANDROID; private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system"); private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media"); @@ -59,6 +72,10 @@ public class Environment { private static volatile StorageVolume sPrimaryVolume; private static StorageVolume getPrimaryVolume() { + if (SystemProperties.getBoolean("config.disable_storage", false)) { + return null; + } + if (sPrimaryVolume == null) { synchronized (sLock) { if (sPrimaryVolume == null) { @@ -94,32 +111,40 @@ public class Environment { public static class UserEnvironment { // TODO: generalize further to create package-specific environment - private final File mExternalStorage; - private final File mExternalStorageAndroidData; - private final File mExternalStorageAndroidMedia; - private final File mExternalStorageAndroidObb; - private final File mMediaStorage; + /** External storage dirs, as visible to vold */ + private final File[] mExternalDirsForVold; + /** External storage dirs, as visible to apps */ + private final File[] mExternalDirsForApp; + /** Primary emulated storage dir for direct access */ + private final File mEmulatedDirForDirect; public UserEnvironment(int userId) { // See storage config details at http://source.android.com/tech/storage/ String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE); - String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); + String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE); + String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); + String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE); if (TextUtils.isEmpty(rawMediaStorage)) { rawMediaStorage = "/data/media"; } - if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) { + ArrayList externalForVold = Lists.newArrayList(); + ArrayList externalForApp = Lists.newArrayList(); + + if (!TextUtils.isEmpty(rawEmulatedTarget)) { // Device has emulated storage; external storage paths should have // userId burned into them. final String rawUserId = Integer.toString(userId); - final File emulatedBase = new File(rawEmulatedStorageTarget); + final File emulatedSourceBase = new File(rawEmulatedSource); + final File emulatedTargetBase = new File(rawEmulatedTarget); final File mediaBase = new File(rawMediaStorage); // /storage/emulated/0 - mExternalStorage = buildPath(emulatedBase, rawUserId); + externalForVold.add(buildPath(emulatedSourceBase, rawUserId)); + externalForApp.add(buildPath(emulatedTargetBase, rawUserId)); // /data/media/0 - mMediaStorage = buildPath(mediaBase, rawUserId); + mEmulatedDirForDirect = buildPath(mediaBase, rawUserId); } else { // Device has physical external storage; use plain paths. @@ -129,54 +154,85 @@ public class Environment { } // /storage/sdcard0 - mExternalStorage = new File(rawExternalStorage); + externalForVold.add(new File(rawExternalStorage)); + externalForApp.add(new File(rawExternalStorage)); // /data/media - mMediaStorage = new File(rawMediaStorage); + mEmulatedDirForDirect = new File(rawMediaStorage); } - mExternalStorageAndroidObb = buildPath(mExternalStorage, DIRECTORY_ANDROID, "obb"); - mExternalStorageAndroidData = buildPath(mExternalStorage, DIRECTORY_ANDROID, "data"); - mExternalStorageAndroidMedia = buildPath(mExternalStorage, DIRECTORY_ANDROID, "media"); + // Splice in any secondary storage paths, but only for owner + final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE); + if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) { + for (String secondaryPath : rawSecondaryStorage.split(":")) { + externalForVold.add(new File(secondaryPath)); + externalForApp.add(new File(secondaryPath)); + } + } + + mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]); + mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]); } + @Deprecated public File getExternalStorageDirectory() { - return mExternalStorage; + return mExternalDirsForApp[0]; + } + + @Deprecated + public File getExternalStoragePublicDirectory(String type) { + return buildExternalStoragePublicDirs(type)[0]; } - public File getExternalStorageObbDirectory() { - return mExternalStorageAndroidObb; + public File[] getExternalDirsForVold() { + return mExternalDirsForVold; } - public File getExternalStoragePublicDirectory(String type) { - return new File(mExternalStorage, type); + public File[] getExternalDirsForApp() { + return mExternalDirsForApp; + } + + public File getMediaDir() { + return mEmulatedDirForDirect; + } + + public File[] buildExternalStoragePublicDirs(String type) { + return buildPaths(mExternalDirsForApp, type); + } + + public File[] buildExternalStorageAndroidDataDirs() { + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA); + } + + public File[] buildExternalStorageAndroidObbDirs() { + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB); } - public File getExternalStorageAndroidDataDir() { - return mExternalStorageAndroidData; + public File[] buildExternalStorageAppDataDirs(String packageName) { + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName); } - public File getExternalStorageAppDataDirectory(String packageName) { - return new File(mExternalStorageAndroidData, packageName); + public File[] buildExternalStorageAppDataDirsForVold(String packageName) { + return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName); } - public File getExternalStorageAppMediaDirectory(String packageName) { - return new File(mExternalStorageAndroidMedia, packageName); + public File[] buildExternalStorageAppMediaDirs(String packageName) { + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName); } - public File getExternalStorageAppObbDirectory(String packageName) { - return new File(mExternalStorageAndroidObb, packageName); + public File[] buildExternalStorageAppObbDirs(String packageName) { + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName); } - public File getExternalStorageAppFilesDirectory(String packageName) { - return new File(new File(mExternalStorageAndroidData, packageName), "files"); + public File[] buildExternalStorageAppObbDirsForVold(String packageName) { + return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName); } - public File getExternalStorageAppCacheDirectory(String packageName) { - return new File(new File(mExternalStorageAndroidData, packageName), "cache"); + public File[] buildExternalStorageAppFilesDirs(String packageName) { + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES); } - public File getMediaStorageDirectory() { - return mMediaStorage; + public File[] buildExternalStorageAppCacheDirs(String packageName) { + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE); } } @@ -225,7 +281,7 @@ public class Environment { */ public static File getMediaStorageDirectory() { throwIfUserRequired(); - return sCurrentUser.getMediaStorageDirectory(); + return sCurrentUser.getMediaDir(); } /** @@ -261,58 +317,64 @@ public class Environment { private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache"); /** - * Gets the Android data directory. + * Return the user data directory. */ public static File getDataDirectory() { return DATA_DIRECTORY; } /** - * Gets the Android external storage directory. This directory may not + * Return the primary external storage directory. This directory may not * currently be accessible if it has been mounted by the user on their * computer, has been removed from the device, or some other problem has - * happened. You can determine its current state with + * happened. You can determine its current state with * {@link #getExternalStorageState()}. - * - *

    Note: don't be confused by the word "external" here. This - * directory can better be thought as media/shared storage. It is a - * filesystem that can hold a relatively large amount of data and that - * is shared across all applications (does not enforce permissions). - * Traditionally this is an SD card, but it may also be implemented as - * built-in storage in a device that is distinct from the protected - * internal storage and can be mounted as a filesystem on a computer.

    - * - *

    On devices with multiple users (as described by {@link UserManager}), - * each user has their own isolated external storage. Applications only - * have access to the external storage for the user they're running as.

    - * - *

    In devices with multiple "external" storage directories (such as - * both secure app storage and mountable shared storage), this directory + *

    + * Note: don't be confused by the word "external" here. This directory + * can better be thought as media/shared storage. It is a filesystem that + * can hold a relatively large amount of data and that is shared across all + * applications (does not enforce permissions). Traditionally this is an SD + * card, but it may also be implemented as built-in storage in a device that + * is distinct from the protected internal storage and can be mounted as a + * filesystem on a computer. + *

    + * On devices with multiple users (as described by {@link UserManager}), + * each user has their own isolated external storage. Applications only have + * access to the external storage for the user they're running as. + *

    + * In devices with multiple "external" storage directories, this directory * represents the "primary" external storage that the user will interact - * with.

    - * - *

    Applications should not directly use this top-level directory, in - * order to avoid polluting the user's root namespace. Any files that are - * private to the application should be placed in a directory returned - * by {@link android.content.Context#getExternalFilesDir + * with. Access to secondary storage is available through + *

    + * Applications should not directly use this top-level directory, in order + * to avoid polluting the user's root namespace. Any files that are private + * to the application should be placed in a directory returned by + * {@link android.content.Context#getExternalFilesDir * Context.getExternalFilesDir}, which the system will take care of deleting - * if the application is uninstalled. Other shared files should be placed - * in one of the directories returned by - * {@link #getExternalStoragePublicDirectory}.

    - * - *

    Writing to this path requires the - * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission. In - * a future platform release, access to this path will require the + * if the application is uninstalled. Other shared files should be placed in + * one of the directories returned by + * {@link #getExternalStoragePublicDirectory}. + *

    + * Writing to this path requires the + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission, + * and starting in read access requires the * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission, - * which is automatically granted if you hold the write permission.

    - * - *

    This path may change between platform versions, so applications - * should only persist relative paths.

    - * - *

    Here is an example of typical code to monitor the state of - * external storage:

    - * - * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java + * which is automatically granted if you hold the write permission. + *

    + * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, if your + * application only needs to store internal data, consider using + * {@link Context#getExternalFilesDir(String)} or + * {@link Context#getExternalCacheDir()}, which require no permissions to + * read or write. + *

    + * This path may change between platform versions, so applications should + * only persist relative paths. + *

    + * Here is an example of typical code to monitor the state of external + * storage: + *

    + * {@sample + * development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java * monitor_storage} * * @see #getExternalStorageState() @@ -320,7 +382,7 @@ public class Environment { */ public static File getExternalStorageDirectory() { throwIfUserRequired(); - return sCurrentUser.getExternalStorageDirectory(); + return sCurrentUser.getExternalDirsForApp()[0]; } /** {@hide} */ @@ -330,7 +392,7 @@ public class Environment { /** {@hide} */ public static File getLegacyExternalStorageObbDirectory() { - return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb"); + return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB); } /** {@hide} */ @@ -342,7 +404,7 @@ public class Environment { /** {@hide} */ public static File getEmulatedStorageObbSource() { // /mnt/shell/emulated/obb - return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), "obb"); + return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), DIR_OBB); } /** @@ -436,7 +498,13 @@ public class Environment { * top-level public directory, as this convention makes no sense elsewhere. */ public static String DIRECTORY_DCIM = "DCIM"; - + + /** + * Standard directory in which to place documents that have been created by + * the user. + */ + public static String DIRECTORY_DOCUMENTS = "Documents"; + /** * Get a top-level public external storage directory for placing files of * a particular type. This is where the user will typically place and @@ -467,139 +535,193 @@ public class Environment { */ public static File getExternalStoragePublicDirectory(String type) { throwIfUserRequired(); - return sCurrentUser.getExternalStoragePublicDirectory(type); + return sCurrentUser.buildExternalStoragePublicDirs(type)[0]; } /** * Returns the path for android-specific data on the SD card. * @hide */ - public static File getExternalStorageAndroidDataDir() { + public static File[] buildExternalStorageAndroidDataDirs() { throwIfUserRequired(); - return sCurrentUser.getExternalStorageAndroidDataDir(); + return sCurrentUser.buildExternalStorageAndroidDataDirs(); } - + /** * Generates the raw path to an application's data * @hide */ - public static File getExternalStorageAppDataDirectory(String packageName) { + public static File[] buildExternalStorageAppDataDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.getExternalStorageAppDataDirectory(packageName); + return sCurrentUser.buildExternalStorageAppDataDirs(packageName); } /** * Generates the raw path to an application's media * @hide */ - public static File getExternalStorageAppMediaDirectory(String packageName) { + public static File[] buildExternalStorageAppMediaDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.getExternalStorageAppMediaDirectory(packageName); + return sCurrentUser.buildExternalStorageAppMediaDirs(packageName); } /** * Generates the raw path to an application's OBB files * @hide */ - public static File getExternalStorageAppObbDirectory(String packageName) { + public static File[] buildExternalStorageAppObbDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.getExternalStorageAppObbDirectory(packageName); + return sCurrentUser.buildExternalStorageAppObbDirs(packageName); } /** * Generates the path to an application's files. * @hide */ - public static File getExternalStorageAppFilesDirectory(String packageName) { + public static File[] buildExternalStorageAppFilesDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.getExternalStorageAppFilesDirectory(packageName); + return sCurrentUser.buildExternalStorageAppFilesDirs(packageName); } /** * Generates the path to an application's cache. * @hide */ - public static File getExternalStorageAppCacheDirectory(String packageName) { + public static File[] buildExternalStorageAppCacheDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.getExternalStorageAppCacheDirectory(packageName); + return sCurrentUser.buildExternalStorageAppCacheDirs(packageName); } /** - * Gets the Android download/cache content directory. + * Return the download/cache content directory. */ public static File getDownloadCacheDirectory() { return DOWNLOAD_CACHE_DIRECTORY; } /** - * {@link #getExternalStorageState()} returns MEDIA_REMOVED if the media is not present. + * Unknown storage state, such as when a path isn't backed by known storage + * media. + * + * @see #getStorageState(File) + */ + public static final String MEDIA_UNKNOWN = "unknown"; + + /** + * Storage state if the media is not present. + * + * @see #getStorageState(File) */ public static final String MEDIA_REMOVED = "removed"; - + /** - * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTED if the media is present - * but not mounted. + * Storage state if the media is present but not mounted. + * + * @see #getStorageState(File) */ public static final String MEDIA_UNMOUNTED = "unmounted"; /** - * {@link #getExternalStorageState()} returns MEDIA_CHECKING if the media is present - * and being disk-checked + * Storage state if the media is present and being disk-checked. + * + * @see #getStorageState(File) */ public static final String MEDIA_CHECKING = "checking"; /** - * {@link #getExternalStorageState()} returns MEDIA_NOFS if the media is present - * but is blank or is using an unsupported filesystem + * Storage state if the media is present but is blank or is using an + * unsupported filesystem. + * + * @see #getStorageState(File) */ public static final String MEDIA_NOFS = "nofs"; /** - * {@link #getExternalStorageState()} returns MEDIA_MOUNTED if the media is present - * and mounted at its mount point with read/write access. + * Storage state if the media is present and mounted at its mount point with + * read/write access. + * + * @see #getStorageState(File) */ public static final String MEDIA_MOUNTED = "mounted"; /** - * {@link #getExternalStorageState()} returns MEDIA_MOUNTED_READ_ONLY if the media is present - * and mounted at its mount point with read only access. + * Storage state if the media is present and mounted at its mount point with + * read-only access. + * + * @see #getStorageState(File) */ public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro"; /** - * {@link #getExternalStorageState()} returns MEDIA_SHARED if the media is present - * not mounted, and shared via USB mass storage. + * Storage state if the media is present not mounted, and shared via USB + * mass storage. + * + * @see #getStorageState(File) */ public static final String MEDIA_SHARED = "shared"; /** - * {@link #getExternalStorageState()} returns MEDIA_BAD_REMOVAL if the media was - * removed before it was unmounted. + * Storage state if the media was removed before it was unmounted. + * + * @see #getStorageState(File) */ public static final String MEDIA_BAD_REMOVAL = "bad_removal"; /** - * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTABLE if the media is present - * but cannot be mounted. Typically this happens if the file system on the - * media is corrupted. + * Storage state if the media is present but cannot be mounted. Typically + * this happens if the file system on the media is corrupted. + * + * @see #getStorageState(File) */ public static final String MEDIA_UNMOUNTABLE = "unmountable"; /** - * Gets the current state of the primary "external" storage device. + * Returns the current state of the primary "external" storage device. * * @see #getExternalStorageDirectory() + * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED}, + * {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING}, + * {@link #MEDIA_NOFS}, {@link #MEDIA_MOUNTED}, + * {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED}, + * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}. */ public static String getExternalStorageState() { + final File externalDir = sCurrentUser.getExternalDirsForApp()[0]; + return getStorageState(externalDir); + } + + /** + * Returns the current state of the storage device that provides the given + * path. + * + * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED}, + * {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING}, + * {@link #MEDIA_NOFS}, {@link #MEDIA_MOUNTED}, + * {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED}, + * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}. + */ + public static String getStorageState(File path) { + final String rawPath; try { - IMountService mountService = IMountService.Stub.asInterface(ServiceManager - .getService("mount")); - final StorageVolume primary = getPrimaryVolume(); - return mountService.getVolumeState(primary.getPath()); - } catch (RemoteException rex) { - Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex); - return Environment.MEDIA_REMOVED; + rawPath = path.getCanonicalPath(); + } catch (IOException e) { + Log.w(TAG, "Failed to resolve target path: " + e); + return Environment.MEDIA_UNKNOWN; } + + try { + final IMountService mountService = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); + final StorageVolume[] volumes = mountService.getVolumeList(); + for (StorageVolume volume : volumes) { + if (rawPath.startsWith(volume.getPath())) { + return mountService.getVolumeState(volume.getPath()); + } + } + } catch (RemoteException e) { + Log.w(TAG, "Failed to find external storage state: " + e); + } + return Environment.MEDIA_UNKNOWN; } /** @@ -663,7 +785,25 @@ public class Environment { } } - private static File buildPath(File base, String... segments) { + /** + * Append path segments to each given base path, returning result. + * + * @hide + */ + public static File[] buildPaths(File[] base, String... segments) { + File[] result = new File[base.length]; + for (int i = 0; i < base.length; i++) { + result[i] = buildPath(base[i], segments); + } + return result; + } + + /** + * Append path segments to given base path, returning result. + * + * @hide + */ + public static File buildPath(File base, String... segments) { File cur = base; for (String segment : segments) { if (cur == null) { diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 9666d9af8cc46ca0a47f45933fa3b4eb63246f0c..4d48fd46dd297813a7b8d94730fb4ef81d9eea19 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -17,10 +17,17 @@ package android.os; import android.util.Log; +import android.util.Slog; + +import libcore.io.ErrnoException; +import libcore.io.IoUtils; +import libcore.io.Libcore; +import libcore.io.OsConstants; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -58,7 +65,84 @@ public class FileUtils { /** Regular expression for safe filenames: no spaces or metacharacters */ private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+"); - public static native int setPermissions(String file, int mode, int uid, int gid); + /** + * Set owner and mode of of given {@link File}. + * + * @param mode to apply through {@code chmod} + * @param uid to apply through {@code chown}, or -1 to leave unchanged + * @param gid to apply through {@code chown}, or -1 to leave unchanged + * @return 0 on success, otherwise errno. + */ + public static int setPermissions(File path, int mode, int uid, int gid) { + return setPermissions(path.getAbsolutePath(), mode, uid, gid); + } + + /** + * Set owner and mode of of given path. + * + * @param mode to apply through {@code chmod} + * @param uid to apply through {@code chown}, or -1 to leave unchanged + * @param gid to apply through {@code chown}, or -1 to leave unchanged + * @return 0 on success, otherwise errno. + */ + public static int setPermissions(String path, int mode, int uid, int gid) { + try { + Libcore.os.chmod(path, mode); + } catch (ErrnoException e) { + Slog.w(TAG, "Failed to chmod(" + path + "): " + e); + return e.errno; + } + + if (uid >= 0 || gid >= 0) { + try { + Libcore.os.chown(path, uid, gid); + } catch (ErrnoException e) { + Slog.w(TAG, "Failed to chown(" + path + "): " + e); + return e.errno; + } + } + + return 0; + } + + /** + * Set owner and mode of of given {@link FileDescriptor}. + * + * @param mode to apply through {@code chmod} + * @param uid to apply through {@code chown}, or -1 to leave unchanged + * @param gid to apply through {@code chown}, or -1 to leave unchanged + * @return 0 on success, otherwise errno. + */ + public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) { + try { + Libcore.os.fchmod(fd, mode); + } catch (ErrnoException e) { + Slog.w(TAG, "Failed to fchmod(): " + e); + return e.errno; + } + + if (uid >= 0 || gid >= 0) { + try { + Libcore.os.fchown(fd, uid, gid); + } catch (ErrnoException e) { + Slog.w(TAG, "Failed to fchown(): " + e); + return e.errno; + } + } + + return 0; + } + + /** + * Return owning UID of given path, otherwise -1. + */ + public static int getUid(String path) { + try { + return Libcore.os.stat(path).st_uid; + } catch (ErrnoException e) { + return -1; + } + } /** returns the FAT file system volume ID for the volume mounted * at the given mount point, or -1 for failure diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 14d8f0765f9d80fbc85385f7bda7d93003e29d58..e6886c4d337d719728a036e5ceb28910f02924b7 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -73,6 +73,9 @@ public class Handler { /** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. + * + * @param msg A {@link android.os.Message Message} object + * @return True if no further handling is desired */ public interface Callback { public boolean handleMessage(Message msg); diff --git a/core/java/android/os/IBatteryPropertiesListener.aidl b/core/java/android/os/IBatteryPropertiesListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7e239249a33c41fab4ea854defec104b0f0a1952 --- /dev/null +++ b/core/java/android/os/IBatteryPropertiesListener.aidl @@ -0,0 +1,27 @@ +/* +** Copyright 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.os; + +import android.os.BatteryProperties; + +/** + * {@hide} + */ + +oneway interface IBatteryPropertiesListener { + void batteryPropertiesChanged(in BatteryProperties props); +} diff --git a/core/java/android/os/IBatteryPropertiesRegistrar.aidl b/core/java/android/os/IBatteryPropertiesRegistrar.aidl new file mode 100644 index 0000000000000000000000000000000000000000..376f6c926c3e03b7dc3e1c7774798627cf821d3b --- /dev/null +++ b/core/java/android/os/IBatteryPropertiesRegistrar.aidl @@ -0,0 +1,28 @@ +/* +** Copyright 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.os; + +import android.os.IBatteryPropertiesListener; + +/** + * {@hide} + */ + +interface IBatteryPropertiesRegistrar { + void registerListener(IBatteryPropertiesListener listener); + void unregisterListener(IBatteryPropertiesListener listener); +} diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index b7bc45fecfa74511900e4ef4e4fc988bc88a7929..a2432d65d85453f8329b61b1efff73a3941ae8af 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -238,7 +238,7 @@ public interface IBinder { *

    You will only receive death notifications for remote binders, * as local binders by definition can't die without you dying as well. * - * @throws Throws {@link RemoteException} if the target IBinder's + * @throws RemoteException if the target IBinder's * process has already died. * * @see #unlinkToDeath @@ -251,13 +251,13 @@ public interface IBinder { * The recipient will no longer be called if this object * dies. * - * @return Returns true if the recipient is successfully + * @return {@code true} if the recipient is successfully * unlinked, assuring you that its * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method - * will not be called. Returns false if the target IBinder has already + * will not be called; {@code false} if the target IBinder has already * died, meaning the method has been (or soon will be) called. * - * @throws Throws {@link java.util.NoSuchElementException} if the given + * @throws java.util.NoSuchElementException if the given * recipient has not been registered with the IBinder, and * the IBinder is still alive. Note that if the recipient * was never registered, but the IBinder has already died, then this diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 45524c82c9e27fce0244d54ea75214810108f655..21b8ae57e36a46156c92d5815e2a4fd8ec5b14b2 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -19,6 +19,7 @@ package android.os; import android.net.InterfaceConfiguration; import android.net.INetworkManagementEventObserver; +import android.net.LinkAddress; import android.net.NetworkStats; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; @@ -116,6 +117,11 @@ interface INetworkManagementService */ void removeSecondaryRoute(String iface, in RouteInfo route); + /** + * Set the specified MTU size + */ + void setMtu(String iface, int mtu); + /** * Shuts down the service */ @@ -254,11 +260,9 @@ interface INetworkManagementService NetworkStats getNetworkStatsUidDetail(int uid); /** - * Return summary of network statistics for the requested pairs of - * tethering interfaces. Even indexes are remote interface, and odd - * indexes are corresponding local interfaces. + * Return summary of network statistics all tethering interfaces. */ - NetworkStats getNetworkStatsTethering(in String[] ifacePairs); + NetworkStats getNetworkStatsTethering(); /** * Set quota for an interface. @@ -343,6 +347,61 @@ interface INetworkManagementService void setFirewallEgressDestRule(String addr, int port, boolean allow); void setFirewallUidRule(int uid, boolean allow); + /** + * Set all packets from users [uid_start,uid_end] to go through interface iface + * iface must already be set for marked forwarding by {@link setMarkedForwarding} + */ + void setUidRangeRoute(String iface, int uid_start, int uid_end); + + /** + * Clears the special routing rules for users [uid_start,uid_end] + */ + void clearUidRangeRoute(String iface, int uid_start, int uid_end); + + /** + * Setup an interface for routing packets marked by {@link setUidRangeRoute} + * + * This sets up a dedicated routing table for packets marked for {@code iface} and adds + * source-NAT rules so that the marked packets have the correct source address. + */ + void setMarkedForwarding(String iface); + + /** + * Removes marked forwarding for an interface + */ + void clearMarkedForwarding(String iface); + + /** + * Get the SO_MARK associated with routing packets for user {@code uid} + */ + int getMarkForUid(int uid); + + /** + * Get the SO_MARK associated with protecting packets from VPN routing rules + */ + int getMarkForProtect(); + + /** + * Route all traffic in {@code route} to {@code iface} setup for marked forwarding + */ + void setMarkedForwardingRoute(String iface, in RouteInfo route); + + /** + * Clear routes set by {@link setMarkedForwardingRoute} + */ + void clearMarkedForwardingRoute(String iface, in RouteInfo route); + + /** + * Exempts {@code host} from the routing set up by {@link setMarkedForwardingRoute} + * All connects to {@code host} will use the global routing table + */ + void setHostExemption(in LinkAddress host); + + /** + * Clears an exemption set by {@link setHostExemption} + */ + void clearHostExemption(in LinkAddress host); + /** * Set a process (pid) to use the name servers associated with the specified interface. */ @@ -353,6 +412,21 @@ interface INetworkManagementService */ void clearDnsInterfaceForPid(int pid); + /** + * Set a range of user ids to use the name servers associated with the specified interface. + */ + void setDnsInterfaceForUidRange(String iface, int uid_start, int uid_end); + + /** + * Clear a user range from being associated with an interface. + */ + void clearDnsInterfaceForUidRange(int uid_start, int uid_end); + + /** + * Clear the mappings from pid to Dns interface and from uid range to Dns interface. + */ + void clearDnsInterfaceMaps(); + /** * Start the clatd (464xlat) service */ diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 6d6d147b8a5e4dafb743e1142f24fdbf15c19f08..4c7bbb48f52b225ffa4fcd7fb1b6fac0edb5093b 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -23,9 +23,10 @@ import android.os.WorkSource; interface IPowerManager { - // WARNING: The first two methods must remain the first two methods because their + // WARNING: The first three methods must remain the first three methods because their // transaction numbers must not change unless IPowerManager.cpp is also updated. - void acquireWakeLock(IBinder lock, int flags, String tag, in WorkSource ws); + void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws); + void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, int uidtoblame); void releaseWakeLock(IBinder lock, int flags); void updateWakeLockWorkSource(IBinder lock, in WorkSource ws); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index a11358a63d4115a433684f396ea2bf6c2b64b99e..3c9d0d904a6c8f8f373a332b34842e676795c26a 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -46,4 +46,8 @@ interface IUserManager { int userHandle); Bundle getApplicationRestrictions(in String packageName); Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle); + boolean setRestrictionsChallenge(in String newPin); + int checkRestrictionsChallenge(in String pin); + boolean hasRestrictionsChallenge(); + void removeRestrictions(); } diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index d5cf771a22bc11063e3465fcea21a4f555e00d81..78c859ee06efe8b3e0b9fa251b99b9727b872fa2 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -289,6 +289,16 @@ public final class Looper { return mQueue; } + /** + * Return whether this looper's thread is currently idle, waiting for new work + * to do. This is intrinsically racy, since its state can change before you get + * the result back. + * @hide + */ + public boolean isIdling() { + return mQueue.isIdling(); + } + public void dump(Printer pw, String prefix) { pw = PrefixPrinter.create(pw, prefix); pw.println(this.toString()); diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index bf7e5caf7024c73228522c0fb32dba034ee1c780..d1b8213d95fcc8437610e604f3cd53c1dfd6e92e 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -39,7 +39,7 @@ public final class MessageQueue { Message mMessages; private final ArrayList mIdleHandlers = new ArrayList(); private IdleHandler[] mPendingIdleHandlers; - private boolean mQuiting; + private boolean mQuitting; // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout. private boolean mBlocked; @@ -52,6 +52,7 @@ public final class MessageQueue { private native static void nativeDestroy(int ptr); private native static void nativePollOnce(int ptr, int timeoutMillis); private native static void nativeWake(int ptr); + private native static boolean nativeIsIdling(int ptr); /** * Callback interface for discovering when a thread is going to block @@ -114,6 +115,8 @@ public final class MessageQueue { } } + // Disposes of the underlying message queue. + // Must only be called on the looper thread or the finalizer. private void dispose() { if (mPtr != 0) { nativeDestroy(mPtr); @@ -124,11 +127,13 @@ public final class MessageQueue { Message next() { int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; - for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } + + // We can assume mPtr != 0 because the loop is obviously still running. + // The looper will not call this method after the loop quits. nativePollOnce(mPtr, nextPollTimeoutMillis); synchronized (this) { @@ -166,7 +171,7 @@ public final class MessageQueue { } // Process the quit message now that all pending messages have been handled. - if (mQuiting) { + if (mQuitting) { dispose(); return null; } @@ -225,18 +230,20 @@ public final class MessageQueue { } synchronized (this) { - if (mQuiting) { + if (mQuitting) { return; } - mQuiting = true; + mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } + + // We can assume mPtr != 0 because mQuitting was previously false. + nativeWake(mPtr); } - nativeWake(mPtr); } int enqueueSyncBarrier(long when) { @@ -269,7 +276,6 @@ public final class MessageQueue { void removeSyncBarrier(int token) { // Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. - final boolean needWake; synchronized (this) { Message prev = null; Message p = mMessages; @@ -281,6 +287,7 @@ public final class MessageQueue { throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed."); } + final boolean needWake; if (prev != null) { prev.next = p.next; needWake = false; @@ -289,9 +296,12 @@ public final class MessageQueue { needWake = mMessages == null || mMessages.target != null; } p.recycle(); - } - if (needWake) { - nativeWake(mPtr); + + // If the loop is quitting then it is already awake. + // We can assume mPtr != 0 when mQuitting is false. + if (needWake && !mQuitting) { + nativeWake(mPtr); + } } } @@ -303,9 +313,8 @@ public final class MessageQueue { throw new AndroidRuntimeException("Message must have a target."); } - boolean needWake; synchronized (this) { - if (mQuiting) { + if (mQuitting) { RuntimeException e = new RuntimeException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); @@ -314,6 +323,7 @@ public final class MessageQueue { msg.when = when; Message p = mMessages; + boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; @@ -338,9 +348,11 @@ public final class MessageQueue { msg.next = p; // invariant: p == prev.next prev.next = msg; } - } - if (needWake) { - nativeWake(mPtr); + + // We can assume mPtr != 0 because mQuitting is false. + if (needWake) { + nativeWake(mPtr); + } } return true; } @@ -379,6 +391,14 @@ public final class MessageQueue { } } + boolean isIdling() { + synchronized (this) { + // If the loop is quitting then it must not be idling. + // We can assume mPtr != 0 when mQuitting is false. + return !mQuitting && nativeIsIdling(mPtr); + } + } + void removeMessages(Handler h, int what, Object object) { if (h == null) { return; diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 0916ea98db86910acd78d25e23a0c663b9817969..02b1998264a2e218da66e771fe80c5b6db67b0bf 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -17,6 +17,7 @@ package android.os; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -177,6 +178,7 @@ import java.util.Set; */ public final class Parcel { private static final boolean DEBUG_RECYCLE = false; + private static final boolean DEBUG_ARRAY_MAP = false; private static final String TAG = "Parcel"; @SuppressWarnings({"UnusedDeclaration"}) @@ -227,6 +229,7 @@ public final class Parcel { private static final int EX_ILLEGAL_ARGUMENT = -3; private static final int EX_NULL_POINTER = -4; private static final int EX_ILLEGAL_STATE = -5; + private static final int EX_NETWORK_MAIN_THREAD = -6; private static final int EX_HAS_REPLY_HEADER = -128; // special; see below private static native int nativeDataSize(int nativePtr); @@ -572,7 +575,7 @@ public final class Parcel { * allows you to avoid mysterious type errors at the point of marshalling. */ public final void writeMap(Map val) { - writeMapInternal((Map) val); + writeMapInternal((Map) val); } /** @@ -592,6 +595,30 @@ public final class Parcel { } } + /** + * Flatten an ArrayMap into the parcel at the current dataPosition(), + * growing dataCapacity() if needed. The Map keys must be String objects. + */ + /* package */ void writeArrayMapInternal(ArrayMap val) { + if (val == null) { + writeInt(-1); + return; + } + final int N = val.size(); + writeInt(N); + if (DEBUG_ARRAY_MAP) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Log.d(TAG, "Writing " + N + " ArrayMap entries", here); + } + for (int i=0; i{@link IllegalStateException} *

  • {@link NullPointerException} *
  • {@link SecurityException} + *
  • {@link NetworkOnMainThreadException} * * * @param e The Exception to be written. @@ -1322,6 +1350,8 @@ public final class Parcel { code = EX_NULL_POINTER; } else if (e instanceof IllegalStateException) { code = EX_ILLEGAL_STATE; + } else if (e instanceof NetworkOnMainThreadException) { + code = EX_NETWORK_MAIN_THREAD; } writeInt(code); StrictMode.clearGatheredViolations(); @@ -1437,6 +1467,8 @@ public final class Parcel { throw new NullPointerException(msg); case EX_ILLEGAL_STATE: throw new IllegalStateException(msg); + case EX_NETWORK_MAIN_THREAD: + throw new NetworkOnMainThreadException(); } throw new RuntimeException("Unknown exception code: " + code + " msg " + msg); @@ -1502,6 +1534,11 @@ public final class Parcel { return fd != null ? new ParcelFileDescriptor(fd) : null; } + /** {@hide} */ + public final FileDescriptor readRawFileDescriptor() { + return nativeReadFileDescriptor(mNativePtr); + } + /*package*/ static native FileDescriptor openFileDescriptor(String file, int mode) throws FileNotFoundException; /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig) @@ -1573,6 +1610,7 @@ public final class Parcel { public final Bundle readBundle(ClassLoader loader) { int length = readInt(); if (length < 0) { + if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length); return null; } @@ -2258,6 +2296,40 @@ public final class Parcel { } } + /* package */ void readArrayMapInternal(ArrayMap outVal, int N, + ClassLoader loader) { + if (DEBUG_ARRAY_MAP) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Log.d(TAG, "Reading " + N + " ArrayMap entries", here); + } + while (N > 0) { + Object key = readValue(loader); + if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read #" + (N-1) + ": key=0x" + + (key != null ? key.hashCode() : 0) + " " + key); + Object value = readValue(loader); + outVal.append(key, value); + N--; + } + } + + /* package */ void readArrayMapSafelyInternal(ArrayMap outVal, int N, + ClassLoader loader) { + if (DEBUG_ARRAY_MAP) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Log.d(TAG, "Reading safely " + N + " ArrayMap entries", here); + } + while (N > 0) { + Object key = readValue(loader); + if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read safe #" + (N-1) + ": key=0x" + + (key != null ? key.hashCode() : 0) + " " + key); + Object value = readValue(loader); + outVal.put(key, value); + N--; + } + } + private void readListInternal(List outVal, int N, ClassLoader loader) { while (N > 0) { diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 3de362c88ad3268dc15d704c8534cb25640d8888..1456387ea83d3440fabb20b4a45f7e1069e6eeed 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -16,8 +16,25 @@ package android.os; +import static libcore.io.OsConstants.AF_UNIX; +import static libcore.io.OsConstants.SEEK_SET; +import static libcore.io.OsConstants.SOCK_STREAM; +import static libcore.io.OsConstants.S_ISLNK; +import static libcore.io.OsConstants.S_ISREG; + +import android.content.BroadcastReceiver; +import android.content.ContentProvider; +import android.util.Log; + import dalvik.system.CloseGuard; +import libcore.io.ErrnoException; +import libcore.io.IoUtils; +import libcore.io.Libcore; +import libcore.io.Memory; +import libcore.io.OsConstants; +import libcore.io.StructStat; + import java.io.Closeable; import java.io.File; import java.io.FileDescriptor; @@ -27,36 +44,80 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.DatagramSocket; import java.net.Socket; +import java.nio.ByteOrder; /** * The FileDescriptor returned by {@link Parcel#readFileDescriptor}, allowing * you to close it when done with it. */ public class ParcelFileDescriptor implements Parcelable, Closeable { - private final FileDescriptor mFileDescriptor; + private static final String TAG = "ParcelFileDescriptor"; + + private final FileDescriptor mFd; + + /** + * Optional socket used to communicate close events, status at close, and + * detect remote process crashes. + */ + private FileDescriptor mCommFd; /** * Wrapped {@link ParcelFileDescriptor}, if any. Used to avoid - * double-closing {@link #mFileDescriptor}. + * double-closing {@link #mFd}. */ private final ParcelFileDescriptor mWrapped; + /** + * Maximum {@link #mStatusBuf} size; longer status messages will be + * truncated. + */ + private static final int MAX_STATUS = 1024; + + /** + * Temporary buffer used by {@link #readCommStatus(FileDescriptor, byte[])}, + * allocated on-demand. + */ + private byte[] mStatusBuf; + + /** + * Status read by {@link #checkError()}, or null if not read yet. + */ + private Status mStatus; + private volatile boolean mClosed; private final CloseGuard mGuard = CloseGuard.get(); /** - * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied - * and this file doesn't already exist, then create the file with - * permissions such that any application can read it. + * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied and + * this file doesn't already exist, then create the file with permissions + * such that any application can read it. + * + * @deprecated Creating world-readable files is very dangerous, and likely + * to cause security holes in applications. It is strongly + * discouraged; instead, applications should use more formal + * mechanism for interactions such as {@link ContentProvider}, + * {@link BroadcastReceiver}, and {@link android.app.Service}. + * There are no guarantees that this access mode will remain on + * a file, such as when it goes through a backup and restore. */ + @Deprecated public static final int MODE_WORLD_READABLE = 0x00000001; /** - * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied - * and this file doesn't already exist, then create the file with - * permissions such that any application can write it. + * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied and + * this file doesn't already exist, then create the file with permissions + * such that any application can write it. + * + * @deprecated Creating world-writable files is very dangerous, and likely + * to cause security holes in applications. It is strongly + * discouraged; instead, applications should use more formal + * mechanism for interactions such as {@link ContentProvider}, + * {@link BroadcastReceiver}, and {@link android.app.Service}. + * There are no guarantees that this access mode will remain on + * a file, such as when it goes through a backup and restore. */ + @Deprecated public static final int MODE_WORLD_WRITEABLE = 0x00000002; /** @@ -89,33 +150,105 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ public static final int MODE_APPEND = 0x02000000; + /** + * Create a new ParcelFileDescriptor wrapped around another descriptor. By + * default all method calls are delegated to the wrapped descriptor. + */ + public ParcelFileDescriptor(ParcelFileDescriptor wrapped) { + // We keep a strong reference to the wrapped PFD, and rely on its + // finalizer to trigger CloseGuard. All calls are delegated to wrapper. + mWrapped = wrapped; + mFd = null; + mCommFd = null; + mClosed = true; + } + + /** {@hide} */ + public ParcelFileDescriptor(FileDescriptor fd) { + this(fd, null); + } + + /** {@hide} */ + public ParcelFileDescriptor(FileDescriptor fd, FileDescriptor commChannel) { + if (fd == null) { + throw new NullPointerException("FileDescriptor must not be null"); + } + mWrapped = null; + mFd = fd; + mCommFd = commChannel; + mGuard.open("close"); + } + /** * Create a new ParcelFileDescriptor accessing a given file. * * @param file The file to be opened. * @param mode The desired access mode, must be one of - * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or - * {@link #MODE_READ_WRITE}; may also be any combination of - * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE}, - * {@link #MODE_WORLD_READABLE}, and {@link #MODE_WORLD_WRITEABLE}. - * - * @return Returns a new ParcelFileDescriptor pointing to the given - * file. + * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or + * {@link #MODE_READ_WRITE}; may also be any combination of + * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE}, + * {@link #MODE_WORLD_READABLE}, and + * {@link #MODE_WORLD_WRITEABLE}. + * @return a new ParcelFileDescriptor pointing to the given file. + * @throws FileNotFoundException if the given file does not exist or can not + * be opened with the requested mode. + * @see #parseMode(String) + */ + public static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException { + final FileDescriptor fd = openInternal(file, mode); + if (fd == null) return null; + + return new ParcelFileDescriptor(fd); + } + + /** + * Create a new ParcelFileDescriptor accessing a given file. * - * @throws FileNotFoundException Throws FileNotFoundException if the given - * file does not exist or can not be opened with the requested mode. + * @param file The file to be opened. + * @param mode The desired access mode, must be one of + * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or + * {@link #MODE_READ_WRITE}; may also be any combination of + * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE}, + * {@link #MODE_WORLD_READABLE}, and + * {@link #MODE_WORLD_WRITEABLE}. + * @param handler to call listener from; must not be null. + * @param listener to be invoked when the returned descriptor has been + * closed; must not be null. + * @return a new ParcelFileDescriptor pointing to the given file. + * @throws FileNotFoundException if the given file does not exist or can not + * be opened with the requested mode. + * @see #parseMode(String) */ - public static ParcelFileDescriptor open(File file, int mode) - throws FileNotFoundException { - String path = file.getPath(); + public static ParcelFileDescriptor open( + File file, int mode, Handler handler, OnCloseListener listener) throws IOException { + if (handler == null) { + throw new IllegalArgumentException("Handler must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + + final FileDescriptor fd = openInternal(file, mode); + if (fd == null) return null; + + final FileDescriptor[] comm = createCommSocketPair(true); + final ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd, comm[0]); - if ((mode&MODE_READ_WRITE) == 0) { + // Kick off thread to watch for status updates + final ListenerBridge bridge = new ListenerBridge(comm[1], handler.getLooper(), listener); + bridge.start(); + + return pfd; + } + + private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException { + if ((mode & MODE_READ_WRITE) == 0) { throw new IllegalArgumentException( "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE"); } - FileDescriptor fd = Parcel.openFileDescriptor(path, mode); - return fd != null ? new ParcelFileDescriptor(fd) : null; + final String path = file.getPath(); + return Parcel.openFileDescriptor(path, mode); } /** @@ -125,8 +258,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * original file descriptor. */ public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException { - FileDescriptor fd = Parcel.dupFileDescriptor(orig); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + final FileDescriptor fd = Libcore.os.dup(orig); + return new ParcelFileDescriptor(fd); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } } /** @@ -136,7 +273,11 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * original file descriptor. */ public ParcelFileDescriptor dup() throws IOException { - return dup(getFileDescriptor()); + if (mWrapped != null) { + return mWrapped.dup(); + } else { + return dup(getFileDescriptor()); + } } /** @@ -150,12 +291,16 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * for a dup of the given fd. */ public static ParcelFileDescriptor fromFd(int fd) throws IOException { - FileDescriptor fdesc = getFileDescriptorFromFd(fd); - return new ParcelFileDescriptor(fdesc); - } + final FileDescriptor original = new FileDescriptor(); + original.setInt$(fd); - // Extracts the file descriptor from the specified socket and returns it untouched - private static native FileDescriptor getFileDescriptorFromFd(int fd) throws IOException; + try { + final FileDescriptor dup = Libcore.os.dup(original); + return new ParcelFileDescriptor(dup); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } /** * Take ownership of a raw native fd in to a new ParcelFileDescriptor. @@ -168,13 +313,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * for the given fd. */ public static ParcelFileDescriptor adoptFd(int fd) { - FileDescriptor fdesc = getFileDescriptorFromFdNoDup(fd); + final FileDescriptor fdesc = new FileDescriptor(); + fdesc.setInt$(fd); + return new ParcelFileDescriptor(fdesc); } - // Extracts the file descriptor from the specified socket and returns it untouched - private static native FileDescriptor getFileDescriptorFromFdNoDup(int fd); - /** * Create a new ParcelFileDescriptor from the specified Socket. The new * ParcelFileDescriptor holds a dup of the original FileDescriptor in @@ -212,15 +356,90 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * is the write side. */ public static ParcelFileDescriptor[] createPipe() throws IOException { - FileDescriptor[] fds = new FileDescriptor[2]; - createPipeNative(fds); - ParcelFileDescriptor[] pfds = new ParcelFileDescriptor[2]; - pfds[0] = new ParcelFileDescriptor(fds[0]); - pfds[1] = new ParcelFileDescriptor(fds[1]); - return pfds; + try { + final FileDescriptor[] fds = Libcore.os.pipe(); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fds[0]), + new ParcelFileDescriptor(fds[1]) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } } - private static native void createPipeNative(FileDescriptor[] outFds) throws IOException; + /** + * Create two ParcelFileDescriptors structured as a data pipe. The first + * ParcelFileDescriptor in the returned array is the read side; the second + * is the write side. + *

    + * The write end has the ability to deliver an error message through + * {@link #closeWithError(String)} which can be handled by the read end + * calling {@link #checkError()}, usually after detecting an EOF. + * This can also be used to detect remote crashes. + */ + public static ParcelFileDescriptor[] createReliablePipe() throws IOException { + try { + final FileDescriptor[] comm = createCommSocketPair(false); + final FileDescriptor[] fds = Libcore.os.pipe(); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fds[0], comm[0]), + new ParcelFileDescriptor(fds[1], comm[1]) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + /** + * Create two ParcelFileDescriptors structured as a pair of sockets + * connected to each other. The two sockets are indistinguishable. + */ + public static ParcelFileDescriptor[] createSocketPair() throws IOException { + try { + final FileDescriptor fd0 = new FileDescriptor(); + final FileDescriptor fd1 = new FileDescriptor(); + Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fd0), + new ParcelFileDescriptor(fd1) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + /** + * Create two ParcelFileDescriptors structured as a pair of sockets + * connected to each other. The two sockets are indistinguishable. + *

    + * Both ends have the ability to deliver an error message through + * {@link #closeWithError(String)} which can be detected by the other end + * calling {@link #checkError()}, usually after detecting an EOF. + * This can also be used to detect remote crashes. + */ + public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException { + try { + final FileDescriptor[] comm = createCommSocketPair(false); + final FileDescriptor fd0 = new FileDescriptor(); + final FileDescriptor fd1 = new FileDescriptor(); + Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fd0, comm[0]), + new ParcelFileDescriptor(fd1, comm[1]) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + private static FileDescriptor[] createCommSocketPair(boolean blocking) throws IOException { + try { + final FileDescriptor comm1 = new FileDescriptor(); + final FileDescriptor comm2 = new FileDescriptor(); + Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, comm1, comm2); + IoUtils.setBlocking(comm1, blocking); + IoUtils.setBlocking(comm2, blocking); + return new FileDescriptor[] { comm1, comm2 }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } /** * @hide Please use createPipe() or ContentProvider.openPipeHelper(). @@ -244,27 +463,90 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { return fd != null ? new ParcelFileDescriptor(fd) : null; } + /** + * Converts a string representing a file mode, such as "rw", into a bitmask suitable for use + * with {@link #open}. + *

    + * @param mode The string representation of the file mode. + * @return A bitmask representing the given file mode. + * @throws IllegalArgumentException if the given string does not match a known file mode. + */ + public static int parseMode(String mode) { + final int modeBits; + if ("r".equals(mode)) { + modeBits = ParcelFileDescriptor.MODE_READ_ONLY; + } else if ("w".equals(mode) || "wt".equals(mode)) { + modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY + | ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_TRUNCATE; + } else if ("wa".equals(mode)) { + modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY + | ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_APPEND; + } else if ("rw".equals(mode)) { + modeBits = ParcelFileDescriptor.MODE_READ_WRITE + | ParcelFileDescriptor.MODE_CREATE; + } else if ("rwt".equals(mode)) { + modeBits = ParcelFileDescriptor.MODE_READ_WRITE + | ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_TRUNCATE; + } else { + throw new IllegalArgumentException("Bad mode '" + mode + "'"); + } + return modeBits; + } + /** * Retrieve the actual FileDescriptor associated with this object. * * @return Returns the FileDescriptor associated with this object. */ public FileDescriptor getFileDescriptor() { - return mFileDescriptor; + if (mWrapped != null) { + return mWrapped.getFileDescriptor(); + } else { + return mFd; + } } /** - * Return the total size of the file representing this fd, as determined - * by stat(). Returns -1 if the fd is not a file. + * Return the total size of the file representing this fd, as determined by + * {@code stat()}. Returns -1 if the fd is not a file. */ - public native long getStatSize(); + public long getStatSize() { + if (mWrapped != null) { + return mWrapped.getStatSize(); + } else { + try { + final StructStat st = Libcore.os.fstat(mFd); + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { + return st.st_size; + } else { + return -1; + } + } catch (ErrnoException e) { + Log.w(TAG, "fstat() failed: " + e); + return -1; + } + } + } /** * This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream, * and I really don't think we want it to be public. * @hide */ - public native long seekTo(long pos); + public long seekTo(long pos) throws IOException { + if (mWrapped != null) { + return mWrapped.seekTo(pos); + } else { + try { + return Libcore.os.lseek(mFd, pos, SEEK_SET); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + } /** * Return the native fd int for this ParcelFileDescriptor. The @@ -272,34 +554,39 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * through this API. */ public int getFd() { - if (mClosed) { - throw new IllegalStateException("Already closed"); + if (mWrapped != null) { + return mWrapped.getFd(); + } else { + if (mClosed) { + throw new IllegalStateException("Already closed"); + } + return mFd.getInt$(); } - return getFdNative(); } - private native int getFdNative(); - /** - * Return the native fd int for this ParcelFileDescriptor and detach it - * from the object here. You are now responsible for closing the fd in - * native code. + * Return the native fd int for this ParcelFileDescriptor and detach it from + * the object here. You are now responsible for closing the fd in native + * code. + *

    + * You should not detach when the original creator of the descriptor is + * expecting a reliable signal through {@link #close()} or + * {@link #closeWithError(String)}. + * + * @see #canDetectErrors() */ public int detachFd() { - if (mClosed) { - throw new IllegalStateException("Already closed"); - } if (mWrapped != null) { - int fd = mWrapped.detachFd(); - mClosed = true; - mGuard.close(); + return mWrapped.detachFd(); + } else { + if (mClosed) { + throw new IllegalStateException("Already closed"); + } + final int fd = getFd(); + Parcel.clearFileDescriptor(mFd); + writeCommStatusAndClose(Status.DETACHED, null); return fd; } - int fd = getFd(); - mClosed = true; - mGuard.close(); - Parcel.clearFileDescriptor(mFileDescriptor); - return fd; } /** @@ -311,16 +598,187 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ @Override public void close() throws IOException { + if (mWrapped != null) { + try { + mWrapped.close(); + } finally { + releaseResources(); + } + } else { + closeWithStatus(Status.OK, null); + } + } + + /** + * Close the ParcelFileDescriptor, informing any peer that an error occurred + * while processing. If the creator of this descriptor is not observing + * errors, it will close normally. + * + * @param msg describing the error; must not be null. + */ + public void closeWithError(String msg) throws IOException { + if (mWrapped != null) { + try { + mWrapped.closeWithError(msg); + } finally { + releaseResources(); + } + } else { + if (msg == null) { + throw new IllegalArgumentException("Message must not be null"); + } + closeWithStatus(Status.ERROR, msg); + } + } + + private void closeWithStatus(int status, String msg) { if (mClosed) return; mClosed = true; mGuard.close(); + // Status MUST be sent before closing actual descriptor + writeCommStatusAndClose(status, msg); + IoUtils.closeQuietly(mFd); + releaseResources(); + } + + /** + * Called when the fd is being closed, for subclasses to release any other resources + * associated with it, such as acquired providers. + * @hide + */ + public void releaseResources() { + } + + private byte[] getOrCreateStatusBuffer() { + if (mStatusBuf == null) { + mStatusBuf = new byte[MAX_STATUS]; + } + return mStatusBuf; + } + + private void writeCommStatusAndClose(int status, String msg) { + if (mCommFd == null) { + // Not reliable, or someone already sent status + if (msg != null) { + Log.w(TAG, "Unable to inform peer: " + msg); + } + return; + } + + if (status == Status.DETACHED) { + Log.w(TAG, "Peer expected signal when closed; unable to deliver after detach"); + } + + try { + try { + if (status != Status.SILENCE) { + final byte[] buf = getOrCreateStatusBuffer(); + int writePtr = 0; + + Memory.pokeInt(buf, writePtr, status, ByteOrder.BIG_ENDIAN); + writePtr += 4; + + if (msg != null) { + final byte[] rawMsg = msg.getBytes(); + final int len = Math.min(rawMsg.length, buf.length - writePtr); + System.arraycopy(rawMsg, 0, buf, writePtr, len); + writePtr += len; + } + + Libcore.os.write(mCommFd, buf, 0, writePtr); + } + } catch (ErrnoException e) { + // Reporting status is best-effort + Log.w(TAG, "Failed to report status: " + e); + } + + if (status != Status.SILENCE) { + // Since we're about to close, read off any remote status. It's + // okay to remember missing here. + mStatus = readCommStatus(mCommFd, getOrCreateStatusBuffer()); + } + } finally { + IoUtils.closeQuietly(mCommFd); + mCommFd = null; + } + } + + private static Status readCommStatus(FileDescriptor comm, byte[] buf) { + try { + final int n = Libcore.os.read(comm, buf, 0, buf.length); + if (n == 0) { + // EOF means they're dead + return new Status(Status.DEAD); + } else { + final int status = Memory.peekInt(buf, 0, ByteOrder.BIG_ENDIAN); + if (status == Status.ERROR) { + final String msg = new String(buf, 4, n - 4); + return new Status(status, msg); + } + return new Status(status); + } + } catch (ErrnoException e) { + if (e.errno == OsConstants.EAGAIN) { + // Remote is still alive, but no status written yet + return null; + } else { + Log.d(TAG, "Failed to read status; assuming dead: " + e); + return new Status(Status.DEAD); + } + } + } + + /** + * Indicates if this ParcelFileDescriptor can communicate and detect remote + * errors/crashes. + * + * @see #checkError() + */ + public boolean canDetectErrors() { if (mWrapped != null) { - // If this is a proxy to another file descriptor, just call through to its - // close method. - mWrapped.close(); + return mWrapped.canDetectErrors(); } else { - Parcel.closeFileDescriptor(mFileDescriptor); + return mCommFd != null; + } + } + + /** + * Detect and throw if the other end of a pipe or socket pair encountered an + * error or crashed. This allows a reader to distinguish between a valid EOF + * and an error/crash. + *

    + * If this ParcelFileDescriptor is unable to detect remote errors, it will + * return silently. + * + * @throws IOException for normal errors. + * @throws FileDescriptorDetachedException + * if the remote side called {@link #detachFd()}. Once detached, the remote + * side is unable to communicate any errors through + * {@link #closeWithError(String)}. + * @see #canDetectErrors() + */ + public void checkError() throws IOException { + if (mWrapped != null) { + mWrapped.checkError(); + } else { + if (mStatus == null) { + if (mCommFd == null) { + Log.w(TAG, "Peer didn't provide a comm channel; unable to check for errors"); + return; + } + + // Try reading status; it might be null if nothing written yet. + // Either way, we keep comm open to write our status later. + mStatus = readCommStatus(mCommFd, getOrCreateStatusBuffer()); + } + + if (mStatus == null || mStatus.status == Status.OK) { + // No status yet, or everything is peachy! + return; + } else { + throw mStatus.asIOException(); + } } } @@ -330,17 +788,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseInputStream extends FileInputStream { - private final ParcelFileDescriptor mFd; + private final ParcelFileDescriptor mPfd; - public AutoCloseInputStream(ParcelFileDescriptor fd) { - super(fd.getFileDescriptor()); - mFd = fd; + public AutoCloseInputStream(ParcelFileDescriptor pfd) { + super(pfd.getFileDescriptor()); + mPfd = pfd; } @Override public void close() throws IOException { try { - mFd.close(); + mPfd.close(); } finally { super.close(); } @@ -353,17 +811,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseOutputStream extends FileOutputStream { - private final ParcelFileDescriptor mFd; + private final ParcelFileDescriptor mPfd; - public AutoCloseOutputStream(ParcelFileDescriptor fd) { - super(fd.getFileDescriptor()); - mFd = fd; + public AutoCloseOutputStream(ParcelFileDescriptor pfd) { + super(pfd.getFileDescriptor()); + mPfd = pfd; } @Override public void close() throws IOException { try { - mFd.close(); + mPfd.close(); } finally { super.close(); } @@ -372,42 +830,37 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { @Override public String toString() { - return "{ParcelFileDescriptor: " + mFileDescriptor + "}"; + if (mWrapped != null) { + return mWrapped.toString(); + } else { + return "{ParcelFileDescriptor: " + mFd + "}"; + } } @Override protected void finalize() throws Throwable { + if (mWrapped != null) { + releaseResources(); + } if (mGuard != null) { mGuard.warnIfOpen(); } try { if (!mClosed) { - close(); + closeWithStatus(Status.LEAKED, null); } } finally { super.finalize(); } } - public ParcelFileDescriptor(ParcelFileDescriptor descriptor) { - mWrapped = descriptor; - mFileDescriptor = mWrapped.mFileDescriptor; - mGuard.open("close"); - } - - /** {@hide} */ - public ParcelFileDescriptor(FileDescriptor descriptor) { - if (descriptor == null) { - throw new NullPointerException("descriptor must not be null"); - } - mWrapped = null; - mFileDescriptor = descriptor; - mGuard.open("close"); - } - @Override public int describeContents() { - return Parcelable.CONTENTS_FILE_DESCRIPTOR; + if (mWrapped != null) { + return mWrapped.describeContents(); + } else { + return Parcelable.CONTENTS_FILE_DESCRIPTOR; + } } /** @@ -417,12 +870,23 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ @Override public void writeToParcel(Parcel out, int flags) { - out.writeFileDescriptor(mFileDescriptor); - if ((flags&PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) { + if (mWrapped != null) { try { - close(); - } catch (IOException e) { - // Empty + mWrapped.writeToParcel(out, flags); + } finally { + releaseResources(); + } + } else { + out.writeFileDescriptor(mFd); + if (mCommFd != null) { + out.writeInt(1); + out.writeFileDescriptor(mCommFd); + } else { + out.writeInt(0); + } + if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) { + // Not a real close, so emit no status + closeWithStatus(Status.SILENCE, null); } } } @@ -431,7 +895,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { = new Parcelable.Creator() { @Override public ParcelFileDescriptor createFromParcel(Parcel in) { - return in.readFileDescriptor(); + final FileDescriptor fd = in.readRawFileDescriptor(); + FileDescriptor commChannel = null; + if (in.readInt() != 0) { + commChannel = in.readRawFileDescriptor(); + } + return new ParcelFileDescriptor(fd, commChannel); } @Override @@ -439,4 +908,118 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { return new ParcelFileDescriptor[size]; } }; + + /** + * Callback indicating that a ParcelFileDescriptor has been closed. + */ + public interface OnCloseListener { + /** + * Event indicating the ParcelFileDescriptor to which this listener was + * attached has been closed. + * + * @param e error state, or {@code null} if closed cleanly. + * If the close event was the result of + * {@link ParcelFileDescriptor#detachFd()}, this will be a + * {@link FileDescriptorDetachedException}. After detach the + * remote side may continue reading/writing to the underlying + * {@link FileDescriptor}, but they can no longer deliver + * reliable close/error events. + */ + public void onClose(IOException e); + } + + /** + * Exception that indicates that the file descriptor was detached. + */ + public static class FileDescriptorDetachedException extends IOException { + + private static final long serialVersionUID = 0xDe7ac4edFdL; + + public FileDescriptorDetachedException() { + super("Remote side is detached"); + } + } + + /** + * Internal class representing a remote status read by + * {@link ParcelFileDescriptor#readCommStatus(FileDescriptor, byte[])}. + */ + private static class Status { + /** Special value indicating remote side died. */ + public static final int DEAD = -2; + /** Special value indicating no status should be written. */ + public static final int SILENCE = -1; + + /** Remote reported that everything went better than expected. */ + public static final int OK = 0; + /** Remote reported error; length and message follow. */ + public static final int ERROR = 1; + /** Remote reported {@link #detachFd()} and went rogue. */ + public static final int DETACHED = 2; + /** Remote reported their object was finalized. */ + public static final int LEAKED = 3; + + public final int status; + public final String msg; + + public Status(int status) { + this(status, null); + } + + public Status(int status, String msg) { + this.status = status; + this.msg = msg; + } + + public IOException asIOException() { + switch (status) { + case DEAD: + return new IOException("Remote side is dead"); + case OK: + return null; + case ERROR: + return new IOException("Remote error: " + msg); + case DETACHED: + return new FileDescriptorDetachedException(); + case LEAKED: + return new IOException("Remote side was leaked"); + default: + return new IOException("Unknown status: " + status); + } + } + } + + /** + * Bridge to watch for remote status, and deliver to listener. Currently + * requires that communication socket is blocking. + */ + private static final class ListenerBridge extends Thread { + // TODO: switch to using Looper to avoid burning a thread + + private FileDescriptor mCommFd; + private final Handler mHandler; + + public ListenerBridge(FileDescriptor comm, Looper looper, final OnCloseListener listener) { + mCommFd = comm; + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + final Status s = (Status) msg.obj; + listener.onClose(s != null ? s.asIOException() : null); + } + }; + } + + @Override + public void run() { + try { + final byte[] buf = new byte[MAX_STATUS]; + final Status status = readCommStatus(mCommFd, buf); + mHandler.obtainMessage(0, status).sendToTarget(); + } finally { + IoUtils.closeQuietly(mCommFd); + mCommFd = null; + } + } + } } diff --git a/core/java/android/os/ParcelableParcel.java b/core/java/android/os/ParcelableParcel.java new file mode 100644 index 0000000000000000000000000000000000000000..11785f1072fdd4925289a0c089557b709f04a2f0 --- /dev/null +++ b/core/java/android/os/ParcelableParcel.java @@ -0,0 +1,75 @@ +/* + * 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.os; + +/** + * Parcelable containing a raw Parcel of data. + * @hide + */ +public class ParcelableParcel implements Parcelable { + final Parcel mParcel; + final ClassLoader mClassLoader; + + public ParcelableParcel(ClassLoader loader) { + mParcel = Parcel.obtain(); + mClassLoader = loader; + } + + public ParcelableParcel(Parcel src, ClassLoader loader) { + mParcel = Parcel.obtain(); + mClassLoader = loader; + int size = src.readInt(); + int pos = src.dataPosition(); + mParcel.appendFrom(src, src.dataPosition(), size); + src.setDataPosition(pos + size); + } + + public Parcel getParcel() { + mParcel.setDataPosition(0); + return mParcel; + } + + public ClassLoader getClassLoader() { + return mClassLoader; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mParcel.dataSize()); + dest.appendFrom(mParcel, 0, mParcel.dataSize()); + } + + public static final Parcelable.ClassLoaderCreator CREATOR + = new Parcelable.ClassLoaderCreator() { + public ParcelableParcel createFromParcel(Parcel in) { + return new ParcelableParcel(in, null); + } + + public ParcelableParcel createFromParcel(Parcel in, ClassLoader loader) { + return new ParcelableParcel(in, loader); + } + + public ParcelableParcel[] newArray(int size) { + return new ParcelableParcel[size]; + } + }; +} diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 736762f67ff31157515e2c707af0f97ec80e6b72..5e0d48949c2210273132b7ed43fd2edabcbf956c 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -224,7 +224,7 @@ public final class PowerManager { /** * Flag for {@link WakeLock#release release(int)} to defer releasing a - * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wake lock until the proximity sensor returns + * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor returns * a negative value. * * {@hide} @@ -407,7 +407,7 @@ public final class PowerManager { */ public WakeLock newWakeLock(int levelAndFlags, String tag) { validateWakeLockParameters(levelAndFlags, tag); - return new WakeLock(levelAndFlags, tag); + return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName()); } /** @hide */ @@ -624,6 +624,7 @@ public final class PowerManager { public final class WakeLock { private final int mFlags; private final String mTag; + private final String mPackageName; private final IBinder mToken; private int mCount; private boolean mRefCounted = true; @@ -636,9 +637,10 @@ public final class PowerManager { } }; - WakeLock(int flags, String tag) { + WakeLock(int flags, String tag, String packageName) { mFlags = flags; mTag = tag; + mPackageName = packageName; mToken = new Binder(); } @@ -714,7 +716,7 @@ public final class PowerManager { // been explicitly released by the keyguard. mHandler.removeCallbacks(mReleaser); try { - mService.acquireWakeLock(mToken, mFlags, mTag, mWorkSource); + mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource); } catch (RemoteException e) { } mHeld = true; diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index d940004922df0be42b8e656446c5098f486169e5..631edd67957b83a9e13285df01b40cea882500e9 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -99,12 +99,6 @@ public class Process { */ public static final int DRM_UID = 1019; - /** - * Defines the GID for the group that allows write access to the SD card. - * @hide - */ - public static final int SDCARD_RW_GID = 1015; - /** * Defines the UID/GID for the group that controls VPN services. * @hide @@ -129,12 +123,19 @@ public class Process { */ public static final int MEDIA_RW_GID = 1023; + /** + * Access to installed package details + * @hide + */ + public static final int PACKAGE_INFO_GID = 1032; + /** * Defines the start of a range of UIDs (and GIDs), going from this * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning * to applications. */ public static final int FIRST_APPLICATION_UID = 10000; + /** * Last of application-specific UIDs starting at * {@link #FIRST_APPLICATION_UID}. @@ -653,6 +654,14 @@ public class Process { return Libcore.os.getpid(); } + /** + * Returns the identifier of this process' parent. + * @hide + */ + public static final int myPpid() { + return Libcore.os.getppid(); + } + /** * Returns the identifier of the calling thread, which be used with * {@link #setThreadPriority(int, int)}. @@ -895,6 +904,19 @@ public class Process { */ public static final native boolean setOomAdj(int pid, int amt); + /** + * Adjust the swappiness level for a process. + * + * @param pid The process identifier to set. + * @param is_increased Whether swappiness should be increased or default. + * + * @return Returns true if the underlying system supports this + * feature, else false. + * + * {@hide} + */ + public static final native boolean setSwappiness(int pid, boolean is_increased); + /** * Change this process's argv[0] parameter. This can be useful to show * more descriptive information in things like the 'ps' command. @@ -978,6 +1000,8 @@ public class Process { /** @hide */ public static final int PROC_PARENS = 0x200; /** @hide */ + public static final int PROC_QUOTES = 0x400; + /** @hide */ public static final int PROC_OUT_STRING = 0x1000; /** @hide */ public static final int PROC_OUT_LONG = 0x2000; diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 85438a1ea794d7b562355fbb61446c9f7438a10d..b692ffdea91cc84918dd75061bec69ce4aad3916 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -244,12 +244,17 @@ public class RecoverySystem { // The signature cert matches a trusted key. Now verify that // the digest in the cert matches the actual file data. - // The verifier in recovery *only* handles SHA1withRSA - // signatures. SignApk.java always uses SHA1withRSA, no - // matter what the cert says to use. Ignore - // cert.getSigAlgName(), and instead use whatever - // algorithm is used by the signature (which should be - // SHA1withRSA). + // The verifier in recovery only handles SHA1withRSA and + // SHA256withRSA signatures. SignApk chooses which to use + // based on the signature algorithm of the cert: + // + // "SHA256withRSA" cert -> "SHA256withRSA" signature + // "SHA1withRSA" cert -> "SHA1withRSA" signature + // "MD5withRSA" cert -> "SHA1withRSA" signature (for backwards compatibility) + // any other cert -> SignApk fails + // + // Here we ignore whatever the cert says, and instead use + // whatever algorithm is used by the signature. String da = sigInfo.getDigestAlgorithm(); String dea = sigInfo.getDigestEncryptionAlgorithm(); diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index d02a3203e9ac3121a7389679cf36388efb53c043..d2a9cdc0a1308c0a6f406d5329aabec08df1c2a9 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -16,7 +16,7 @@ package android.os; -import java.util.HashMap; +import android.util.ArrayMap; /** * Takes care of the grunt work of maintaining a list of remote interfaces, @@ -47,8 +47,8 @@ import java.util.HashMap; * implements the {@link #onCallbackDied} method. */ public class RemoteCallbackList { - /*package*/ HashMap mCallbacks - = new HashMap(); + /*package*/ ArrayMap mCallbacks + = new ArrayMap(); private Object[] mActiveBroadcast; private int mBroadcastCount = -1; private boolean mKilled = false; @@ -159,7 +159,8 @@ public class RemoteCallbackList { */ public void kill() { synchronized (mCallbacks) { - for (Callback cb : mCallbacks.values()) { + for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) { + Callback cb = mCallbacks.valueAt(cbi); cb.mCallback.asBinder().unlinkToDeath(cb, 0); } mCallbacks.clear(); @@ -238,11 +239,10 @@ public class RemoteCallbackList { if (active == null || active.length < N) { mActiveBroadcast = active = new Object[N]; } - int i=0; - for (Callback cb : mCallbacks.values()) { - active[i++] = cb; + for (int i=0; i + threadAndroidPolicy = new ThreadLocal() { + @Override + protected AndroidBlockGuardPolicy initialValue() { + return new AndroidBlockGuardPolicy(0); + } + }; + private static boolean tooManyViolationsThisLoop() { return violationsBeingTimed.get().size() >= MAX_OFFENSES_PER_LOOP; } @@ -1069,7 +1081,7 @@ public final class StrictMode { // Map from violation stacktrace hashcode -> uptimeMillis of // last violation. No locking needed, as this is only // accessed by the same thread. - private final HashMap mLastViolationTime = new HashMap(); + private ArrayMap mLastViolationTime; public AndroidBlockGuardPolicy(final int policyMask) { mPolicyMask = policyMask; @@ -1279,8 +1291,13 @@ public final class StrictMode { // Not perfect, but fast and good enough for dup suppression. Integer crashFingerprint = info.hashCode(); long lastViolationTime = 0; - if (mLastViolationTime.containsKey(crashFingerprint)) { - lastViolationTime = mLastViolationTime.get(crashFingerprint); + if (mLastViolationTime != null) { + Long vtime = mLastViolationTime.get(crashFingerprint); + if (vtime != null) { + lastViolationTime = vtime; + } + } else { + mLastViolationTime = new ArrayMap(1); } long now = SystemClock.uptimeMillis(); mLastViolationTime.put(crashFingerprint, now); @@ -1684,7 +1701,9 @@ public final class StrictMode { /* package */ static void readAndHandleBinderCallViolations(Parcel p) { // Our own stack trace to append StringWriter sw = new StringWriter(); - new LogStackTrace().printStackTrace(new PrintWriter(sw)); + PrintWriter pw = new FastPrintWriter(sw, false, 256); + new LogStackTrace().printStackTrace(pw); + pw.flush(); String ourStack = sw.toString(); int policyMask = getThreadPolicyMask(); diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index e66fb2854aa70b1297fe2d7c7bd41d2613eb6d84..700f80d1991ccf371ee5a58e41e761a83326dfb5 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -39,7 +39,7 @@ public class SystemVibrator extends Vibrator { } public SystemVibrator(Context context) { - mPackageName = context.getBasePackageName(); + mPackageName = context.getOpPackageName(); mService = IVibratorService.Stub.asInterface( ServiceManager.getService("vibrator")); } diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index e53cb5ed4e1eb81c45436dcfa2ed5466793e0892..bb3d296acd7999451a5fe5cd55017d93bc9dea3c 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -67,6 +67,8 @@ public final class Trace { public static final long TRACE_TAG_RESOURCES = 1L << 13; /** @hide */ public static final long TRACE_TAG_DALVIK = 1L << 14; + /** @hide */ + public static final long TRACE_TAG_RS = 1L << 15; private static final long TRACE_TAG_NOT_READY = 1L << 63; private static final int MAX_SECTION_NAME_LEN = 127; diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index d2052539ccb9cc5bfb32d7acef66eaaeca6c0bfc..6e693a41230f638358ec474980ded164cc54b767 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -168,8 +168,11 @@ public final class UserHandle implements Parcelable { if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) { sb.append('i'); sb.append(appId - Process.FIRST_ISOLATED_UID); - } else { + } else if (appId >= Process.FIRST_APPLICATION_UID) { sb.append('a'); + sb.append(appId - Process.FIRST_APPLICATION_UID); + } else { + sb.append('s'); sb.append(appId); } } @@ -190,8 +193,11 @@ public final class UserHandle implements Parcelable { if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) { pw.print('i'); pw.print(appId - Process.FIRST_ISOLATED_UID); - } else { + } else if (appId >= Process.FIRST_APPLICATION_UID) { pw.print('a'); + pw.print(appId - Process.FIRST_APPLICATION_UID); + } else { + pw.print('s'); pw.print(appId); } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index cb5ed4f2c15dcb738c38cecce0546bbd0469c93c..a3752a1a2d95628f650e1453822c46f28f846b47 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -140,6 +140,13 @@ public class UserManager { */ public static final String DISALLOW_REMOVE_USER = "no_remove_user"; + /** @hide */ + public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3; + /** @hide */ + public static final int PIN_VERIFICATION_FAILED_NOT_SET = -2; + /** @hide */ + public static final int PIN_VERIFICATION_SUCCESS = -1; + private static UserManager sInstance = null; /** @hide */ @@ -340,7 +347,18 @@ public class UserManager { * @param restrictionKey the string key representing the restriction */ public boolean hasUserRestriction(String restrictionKey) { - return getUserRestrictions().getBoolean(restrictionKey, false); + return hasUserRestriction(restrictionKey, Process.myUserHandle()); + } + + /** + * @hide + * Returns whether the given user has been disallowed from performing certain actions + * or setting certain settings. + * @param restrictionKey the string key representing the restriction + * @param userHandle the UserHandle of the user for whom to retrieve the restrictions. + */ + public boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) { + return getUserRestrictions(userHandle).getBoolean(restrictionKey, false); } /** @@ -620,4 +638,64 @@ public class UserManager { Log.w(TAG, "Could not set application restrictions for user " + user.getIdentifier()); } } + + /** + * Sets a new challenge PIN for restrictions. This is only for use by pre-installed + * apps and requires the MANAGE_USERS permission. + * @param newPin the PIN to use for challenge dialogs. + * @return Returns true if the challenge PIN was set successfully. + */ + public boolean setRestrictionsChallenge(String newPin) { + try { + return mService.setRestrictionsChallenge(newPin); + } catch (RemoteException re) { + Log.w(TAG, "Could not change restrictions pin"); + } + return false; + } + + /** + * @hide + * @param pin The PIN to verify, or null to get the number of milliseconds to wait for before + * allowing the user to enter the PIN. + * @return Returns a positive number (including zero) for how many milliseconds before + * you can accept another PIN, when the input is null or the input doesn't match the saved PIN. + * Returns {@link #PIN_VERIFICATION_SUCCESS} if the input matches the saved PIN. Returns + * {@link #PIN_VERIFICATION_FAILED_NOT_SET} if there is no PIN set. + */ + public int checkRestrictionsChallenge(String pin) { + try { + return mService.checkRestrictionsChallenge(pin); + } catch (RemoteException re) { + Log.w(TAG, "Could not check restrictions pin"); + } + return PIN_VERIFICATION_FAILED_INCORRECT; + } + + /** + * @hide + * Checks whether the user has restrictions that are PIN-protected. An application that + * participates in restrictions can check if the owner has requested a PIN challenge for + * any restricted operations. If there is a PIN in effect, the application should launch + * the PIN challenge activity {@link android.content.Intent#ACTION_RESTRICTIONS_CHALLENGE}. + * @see android.content.Intent#ACTION_RESTRICTIONS_CHALLENGE + * @return whether a restrictions PIN is in effect. + */ + public boolean hasRestrictionsChallenge() { + try { + return mService.hasRestrictionsChallenge(); + } catch (RemoteException re) { + Log.w(TAG, "Could not change restrictions pin"); + } + return false; + } + + /** @hide */ + public void removeRestrictions() { + try { + mService.removeRestrictions(); + } catch (RemoteException re) { + Log.w(TAG, "Could not change restrictions pin"); + } + } } diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index b79bdee93d971d190cdd891d48c8d36f0b7080d8..f8da87ab4702ac51108c9319f761da8dc7efceb9 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -96,6 +96,30 @@ public class WorkSource implements Parcelable { return mNames != null ? mNames[index] : null; } + /** + * Clear names from this WorkSource. Uids are left intact. + * + *

    Useful when combining with another WorkSource that doesn't have names. + * @hide + */ + public void clearNames() { + if (mNames != null) { + mNames = null; + // Clear out any duplicate uids now that we don't have names to disambiguate them. + int destIndex = 1; + int newNum = mNum; + for (int sourceIndex = 1; sourceIndex < mNum; sourceIndex++) { + if (mUids[sourceIndex] == mUids[sourceIndex - 1]) { + newNum--; + } else { + mUids[destIndex] = mUids[sourceIndex]; + destIndex++; + } + } + mNum = newNum; + } + } + /** * Clear this WorkSource to be empty. */ @@ -199,7 +223,6 @@ public class WorkSource implements Parcelable { } mUids[0] = uid; mNames[0] = name; - mNames = null; } /** @hide */ diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index fc186177ae18c148b47cd8618c8b33a750a9ddb6..51ba2f6a74c71560cfcda82f5bf6db0b3170779d 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -20,9 +20,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; -import android.os.Parcelable; import android.os.RemoteException; -import android.os.storage.StorageVolume; /** * WARNING! Update IMountService.h and IMountService.cpp if you change this @@ -737,7 +735,25 @@ public interface IMountService extends IInterface { _data.recycle(); } return _result; + } + @Override + public int mkdirs(String callingPkg, String path) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(callingPkg); + _data.writeString(path); + mRemote.transact(Stub.TRANSACTION_mkdirs, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; } } @@ -811,6 +827,8 @@ public interface IMountService extends IInterface { static final int TRANSACTION_fixPermissionsSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 33; + static final int TRANSACTION_mkdirs = IBinder.FIRST_CALL_TRANSACTION + 34; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1154,6 +1172,15 @@ public interface IMountService extends IInterface { reply.writeInt(resultCode); return true; } + case TRANSACTION_mkdirs: { + data.enforceInterface(DESCRIPTOR); + String callingPkg = data.readString(); + String path = data.readString(); + int result = mkdirs(callingPkg, path); + reply.writeNoException(); + reply.writeInt(result); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1376,4 +1403,13 @@ public interface IMountService extends IInterface { */ public int fixPermissionsSecureContainer(String id, int gid, String filename) throws RemoteException; + + /** + * Ensure that all directories along given path exist, creating parent + * directories as needed. Validates that given path is absolute and that it + * contains no relative "." or ".." paths or symlinks. Also ensures that + * path belongs to a volume managed by vold, and that path is either + * external storage data or OBB directory belonging to calling app. + */ + public int mkdirs(String callingPkg, String path) throws RemoteException; } diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java index f44cbe40f7ae371ec57fa9b73763023c1dea7113..9edf112d630b1c26cbcf037208cbce566ee2be54 100644 --- a/core/java/android/preference/ListPreference.java +++ b/core/java/android/preference/ListPreference.java @@ -16,13 +16,13 @@ package android.preference; - import android.app.AlertDialog.Builder; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.AttributeSet; /** @@ -41,6 +41,7 @@ public class ListPreference extends DialogPreference { private String mValue; private String mSummary; private int mClickedDialogEntryIndex; + private boolean mValueSet; public ListPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -130,9 +131,16 @@ public class ListPreference extends DialogPreference { * @param value The value to set for the key. */ public void setValue(String value) { - mValue = value; - - persistString(value); + // Always persist/notify the first time. + final boolean changed = !TextUtils.equals(mValue, value); + if (changed || !mValueSet) { + mValue = value; + mValueSet = true; + persistString(value); + if (changed) { + notifyChanged(); + } + } } /** diff --git a/core/java/android/preference/OnDependencyChangeListener.java b/core/java/android/preference/OnDependencyChangeListener.java deleted file mode 100644 index ce25e34ec9f5bf9d37044825a02e73200f0f2dab..0000000000000000000000000000000000000000 --- a/core/java/android/preference/OnDependencyChangeListener.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2007 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.preference; - -/** - * Interface definition for a callback to be invoked when this - * {@link Preference} changes with respect to enabling/disabling - * dependents. - */ -interface OnDependencyChangeListener { - /** - * Called when this preference has changed in a way that dependents should - * care to change their state. - * - * @param disablesDependent Whether the dependent should be disabled. - */ - void onDependencyChanged(Preference dependency, boolean disablesDependent); -} diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 6c0296537b50d92ff47892898c34fb80fc58d1da..37a810236832141201fdeaf9fa93db02c34e7b00 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -77,7 +77,7 @@ import java.util.Set; * @attr ref android.R.styleable#Preference_defaultValue * @attr ref android.R.styleable#Preference_shouldDisableView */ -public class Preference implements Comparable, OnDependencyChangeListener { +public class Preference implements Comparable { /** * Specify for {@link #setOrder(int)} if a specific order is not required. */ @@ -115,6 +115,7 @@ public class Preference implements Comparable, OnDependencyChangeLis private String mDependencyKey; private Object mDefaultValue; private boolean mDependencyMet = true; + private boolean mParentDependencyMet = true; /** * @see #setShouldDisableView(boolean) @@ -123,7 +124,7 @@ public class Preference implements Comparable, OnDependencyChangeLis private int mLayoutResId = com.android.internal.R.layout.preference; private int mWidgetLayoutResId; - private boolean mHasSpecifiedLayout = false; + private boolean mCanRecycleLayout = true; private OnPreferenceChangeInternalListener mListener; @@ -274,9 +275,10 @@ public class Preference implements Comparable, OnDependencyChangeLis } a.recycle(); - if (!getClass().getName().startsWith("android.preference")) { - // For subclasses not in this package, assume the worst and don't cache views - mHasSpecifiedLayout = true; + if (!getClass().getName().startsWith("android.preference") + && !getClass().getName().startsWith("com.android")) { + // For non-framework subclasses, assume the worst and don't cache views. + mCanRecycleLayout = false; } } @@ -398,7 +400,7 @@ public class Preference implements Comparable, OnDependencyChangeLis public void setLayoutResource(int layoutResId) { if (layoutResId != mLayoutResId) { // Layout changed - mHasSpecifiedLayout = true; + mCanRecycleLayout = false; } mLayoutResId = layoutResId; @@ -414,7 +416,7 @@ public class Preference implements Comparable, OnDependencyChangeLis } /** - * Sets The layout for the controllable widget portion of this Preference. This + * Sets the layout for the controllable widget portion of this Preference. This * is inflated into the main layout. For example, a {@link CheckBoxPreference} * would specify a custom layout (consisting of just the CheckBox) here, * instead of creating its own main layout. @@ -426,7 +428,7 @@ public class Preference implements Comparable, OnDependencyChangeLis public void setWidgetLayoutResource(int widgetLayoutResId) { if (widgetLayoutResId != mWidgetLayoutResId) { // Layout changed - mHasSpecifiedLayout = true; + mCanRecycleLayout = false; } mWidgetLayoutResId = widgetLayoutResId; } @@ -733,7 +735,7 @@ public class Preference implements Comparable, OnDependencyChangeLis * @return True if this Preference is enabled, false otherwise. */ public boolean isEnabled() { - return mEnabled && mDependencyMet; + return mEnabled && mDependencyMet && mParentDependencyMet; } /** @@ -1259,7 +1261,24 @@ public class Preference implements Comparable, OnDependencyChangeLis notifyChanged(); } } - + + /** + * Called when the implicit parent dependency changes. + * + * @param parent The Preference that this Preference depends on. + * @param disableChild Set true to disable this Preference. + */ + public void onParentChanged(Preference parent, boolean disableChild) { + if (mParentDependencyMet == disableChild) { + mParentDependencyMet = !disableChild; + + // Enabled state can change dependent preferences' states, so notify + notifyDependencyChange(shouldDisableDependents()); + + notifyChanged(); + } + } + /** * Checks whether this preference's dependents should currently be * disabled. @@ -1641,8 +1660,8 @@ public class Preference implements Comparable, OnDependencyChangeLis return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue); } - boolean hasSpecifiedLayout() { - return mHasSpecifiedLayout; + boolean canRecycleLayout() { + return mCanRecycleLayout; } @Override diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index a9ee96ec76c793fc348037c006988bf3c40b37e6..2ab5a91a7013e9719fd366a596494567329db858 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -33,6 +33,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.util.TypedValue; import android.util.Xml; import android.view.LayoutInflater; @@ -124,6 +125,8 @@ public abstract class PreferenceActivity extends ListActivity implements PreferenceManager.OnPreferenceTreeClickListener, PreferenceFragment.OnPreferenceStartFragmentCallback { + private static final String TAG = "PreferenceActivity"; + // Constants for state save/restore private static final String HEADERS_TAG = ":android:headers"; private static final String CUR_HEADER_TAG = ":android:cur_header"; @@ -132,6 +135,9 @@ public abstract class PreferenceActivity extends ListActivity implements /** * When starting this activity, the invoking Intent can contain this extra * string to specify which fragment should be initially displayed. + *

    Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity + * will call isValidFragment() to confirm that the fragment class name is valid for this + * activity. */ public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment"; @@ -299,7 +305,7 @@ public abstract class PreferenceActivity extends ListActivity implements * are valid. */ public static final long HEADER_ID_UNDEFINED = -1; - + /** * Description of a single Header item that the user can select. */ @@ -877,7 +883,25 @@ public abstract class PreferenceActivity extends ListActivity implements } finally { if (parser != null) parser.close(); } + } + /** + * Subclasses should override this method and verify that the given fragment is a valid type + * to be attached to this activity. The default implementation returns true for + * apps built for android:targetSdkVersion older than + * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception. + * @param fragmentName the class name of the Fragment about to be attached to this activity. + * @return true if the fragment class name is valid for this Activity and false otherwise. + */ + protected boolean isValidFragment(String fragmentName) { + if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) { + throw new RuntimeException( + "Subclasses of PreferenceActivity must override isValidFragment(String)" + + " to verify that the Fragment class is valid! " + this.getClass().getName() + + " has not checked if fragment " + fragmentName + " is valid."); + } else { + return true; + } } /** @@ -973,6 +997,9 @@ public abstract class PreferenceActivity extends ListActivity implements @Override protected void onListItemClick(ListView l, View v, int position, long id) { + if (!isResumed()) { + return; + } super.onListItemClick(l, v, position, id); if (mAdapter != null) { @@ -1083,6 +1110,7 @@ public abstract class PreferenceActivity extends ListActivity implements try { mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs; } catch (ClassCastException e) { + setTitle(title); return; } if (mFragmentBreadCrumbs == null) { @@ -1096,12 +1124,17 @@ public abstract class PreferenceActivity extends ListActivity implements // Hide the breadcrumb section completely for single-pane View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section); if (bcSection != null) bcSection.setVisibility(View.GONE); + setTitle(title); } mFragmentBreadCrumbs.setMaxVisible(2); mFragmentBreadCrumbs.setActivity(this); } - mFragmentBreadCrumbs.setTitle(title, shortTitle); - mFragmentBreadCrumbs.setParentTitle(null, null, null); + if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) { + setTitle(title); + } else { + mFragmentBreadCrumbs.setTitle(title, shortTitle); + mFragmentBreadCrumbs.setParentTitle(null, null, null); + } } /** @@ -1143,6 +1176,10 @@ public abstract class PreferenceActivity extends ListActivity implements private void switchToHeaderInner(String fragmentName, Bundle args, int direction) { getFragmentManager().popBackStack(BACK_STACK_PREFS, FragmentManager.POP_BACK_STACK_INCLUSIVE); + if (!isValidFragment(fragmentName)) { + throw new IllegalArgumentException("Invalid fragment for this activity: " + + fragmentName); + } Fragment f = Fragment.instantiate(this, fragmentName, args); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); @@ -1249,7 +1286,7 @@ public abstract class PreferenceActivity extends ListActivity implements } /** - * Start a new fragment containing a preference panel. If the prefences + * Start a new fragment containing a preference panel. If the preferences * are being displayed in multi-pane mode, the given fragment class will * be instantiated and placed in the appropriate pane. If running in * single-pane mode, a new activity will be launched in which to show the @@ -1288,7 +1325,7 @@ public abstract class PreferenceActivity extends ListActivity implements transaction.commitAllowingStateLoss(); } } - + /** * Called by a preference panel fragment to finish itself. * diff --git a/core/java/android/preference/PreferenceCategory.java b/core/java/android/preference/PreferenceCategory.java index d8af3248f7d2674017a7c67948d14928a13a4246..229a96a187355894480f09ef964ad67073805eda 100644 --- a/core/java/android/preference/PreferenceCategory.java +++ b/core/java/android/preference/PreferenceCategory.java @@ -62,4 +62,8 @@ public class PreferenceCategory extends PreferenceGroup { return false; } + @Override + public boolean shouldDisableDependents() { + return !super.isEnabled(); + } } diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java index f33a6be9ff5595a0b1110f8ed5b1169ae6641798..5f8c78d269e624d7b73628bf1c60b046c779e9c7 100644 --- a/core/java/android/preference/PreferenceGroup.java +++ b/core/java/android/preference/PreferenceGroup.java @@ -210,10 +210,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla * @return Whether to allow adding the preference (true), or not (false). */ protected boolean onPrepareAddPreference(Preference preference) { - if (!super.isEnabled()) { - preference.setEnabled(false); - } - + preference.onParentChanged(this, shouldDisableDependents()); return true; } @@ -290,13 +287,14 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla } @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - - // Dispatch to all contained preferences + public void notifyDependencyChange(boolean disableDependents) { + super.notifyDependencyChange(disableDependents); + + // Child preferences have an implicit dependency on their containing + // group. Dispatch dependency change to all contained preferences. final int preferenceCount = getPreferenceCount(); for (int i = 0; i < preferenceCount; i++) { - getPreference(i).setEnabled(enabled); + getPreference(i).onParentChanged(this, disableDependents); } } diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index a908ecdc695dfec93c954c9aa5302c836f29080b..23d0a19613baca52a9f4af540a23e08e0ab58b2d 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -153,7 +153,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn preferences.add(preference); - if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) { + if (!mHasReturnedViewTypeCount && preference.canRecycleLayout()) { addPreferenceClassName(preference); } @@ -255,7 +255,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn } final Preference preference = this.getItem(position); - if (preference.hasSpecifiedLayout()) { + if (!preference.canRecycleLayout()) { return IGNORE_ITEM_VIEW_TYPE; } diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java index c6498791997cd41cbd30279bb5281bd76bfa2e12..af839533404560f9ea811956af57f8e58b7c4503 100644 --- a/core/java/android/preference/TwoStatePreference.java +++ b/core/java/android/preference/TwoStatePreference.java @@ -21,6 +21,7 @@ import android.content.SharedPreferences; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -215,17 +216,17 @@ public abstract class TwoStatePreference extends Preference { TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary); if (summaryView != null) { boolean useDefaultSummary = true; - if (mChecked && mSummaryOn != null) { + if (mChecked && !TextUtils.isEmpty(mSummaryOn)) { summaryView.setText(mSummaryOn); useDefaultSummary = false; - } else if (!mChecked && mSummaryOff != null) { + } else if (!mChecked && !TextUtils.isEmpty(mSummaryOff)) { summaryView.setText(mSummaryOff); useDefaultSummary = false; } if (useDefaultSummary) { final CharSequence summary = getSummary(); - if (summary != null) { + if (!TextUtils.isEmpty(summary)) { summaryView.setText(summary); useDefaultSummary = false; } diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index caf55d70226efdf723a6644a48e076eed4b06a53..dc683a62fcaddebf1ae4874dd550e415cce23875 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -25,11 +25,14 @@ import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.provider.Settings; import android.provider.Settings.System; import android.util.AttributeSet; +import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.widget.SeekBar; @@ -115,7 +118,7 @@ public class VolumePreference extends SeekBarDialogPreference implements public void onActivityStop() { if (mSeekBarVolumizer != null) { - mSeekBarVolumizer.stopSample(); + mSeekBarVolumizer.postStopSample(); } } @@ -220,10 +223,10 @@ public class VolumePreference extends SeekBarDialogPreference implements /** * Turns a {@link SeekBar} into a volume control. */ - public class SeekBarVolumizer implements OnSeekBarChangeListener, Runnable { + public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback { private Context mContext; - private Handler mHandler = new Handler(); + private Handler mHandler; private AudioManager mAudioManager; private int mStreamType; @@ -234,6 +237,11 @@ public class VolumePreference extends SeekBarDialogPreference implements private SeekBar mSeekBar; private int mVolumeBeforeMute = -1; + private static final int MSG_SET_STREAM_VOLUME = 0; + private static final int MSG_START_SAMPLE = 1; + private static final int MSG_STOP_SAMPLE = 2; + private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; + private ContentObserver mVolumeObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -255,6 +263,10 @@ public class VolumePreference extends SeekBarDialogPreference implements mStreamType = streamType; mSeekBar = seekBar; + HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); + thread.start(); + mHandler = new Handler(thread.getLooper(), this); + initSeekBar(seekBar, defaultUri); } @@ -285,8 +297,54 @@ public class VolumePreference extends SeekBarDialogPreference implements } } + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_STREAM_VOLUME: + mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0); + break; + case MSG_START_SAMPLE: + onStartSample(); + break; + case MSG_STOP_SAMPLE: + onStopSample(); + break; + default: + Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what); + } + return true; + } + + private void postStartSample() { + mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE), + isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0); + } + + private void onStartSample() { + if (!isSamplePlaying()) { + onSampleStarting(this); + if (mRingtone != null) { + mRingtone.play(); + } + } + } + + private void postStopSample() { + // remove pending delayed start messages + mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.removeMessages(MSG_STOP_SAMPLE); + mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE)); + } + + private void onStopSample() { + if (mRingtone != null) { + mRingtone.stop(); + } + } + public void stop() { - stopSample(); + postStopSample(); mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); mSeekBar.setOnSeekBarChangeListener(null); } @@ -307,21 +365,15 @@ public class VolumePreference extends SeekBarDialogPreference implements void postSetVolume(int progress) { // Do the volume changing separately to give responsive UI mLastProgress = progress; - mHandler.removeCallbacks(this); - mHandler.post(this); + mHandler.removeMessages(MSG_SET_STREAM_VOLUME); + mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME)); } public void onStartTrackingTouch(SeekBar seekBar) { } public void onStopTrackingTouch(SeekBar seekBar) { - if (!isSamplePlaying()) { - startSample(); - } - } - - public void run() { - mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0); + postStartSample(); } public boolean isSamplePlaying() { @@ -329,16 +381,11 @@ public class VolumePreference extends SeekBarDialogPreference implements } public void startSample() { - onSampleStarting(this); - if (mRingtone != null) { - mRingtone.play(); - } + postStartSample(); } public void stopSample() { - if (mRingtone != null) { - mRingtone.stop(); - } + postStopSample(); } public SeekBar getSeekBar() { @@ -347,23 +394,21 @@ public class VolumePreference extends SeekBarDialogPreference implements public void changeVolumeBy(int amount) { mSeekBar.incrementProgressBy(amount); - if (!isSamplePlaying()) { - startSample(); - } postSetVolume(mSeekBar.getProgress()); + postStartSample(); mVolumeBeforeMute = -1; } public void muteVolume() { if (mVolumeBeforeMute != -1) { mSeekBar.setProgress(mVolumeBeforeMute); - startSample(); postSetVolume(mVolumeBeforeMute); + postStartSample(); mVolumeBeforeMute = -1; } else { mVolumeBeforeMute = mSeekBar.getProgress(); mSeekBar.setProgress(0); - stopSample(); + postStopSample(); postSetVolume(0); } } diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..43b8c309451760805e56b30fa84dda5a6500c56d --- /dev/null +++ b/core/java/android/print/ILayoutResultCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.PrintDocumentInfo; + +/** + * Callback for observing the result of android.print.PrintAdapter#onLayout. + * + * @hide + */ +oneway interface ILayoutResultCallback { + void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence); + void onLayoutFailed(CharSequence error, int sequence); +} diff --git a/core/java/android/print/IPrintClient.aidl b/core/java/android/print/IPrintClient.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3f39d0887954a2d68923aa631a1694a22a97c019 --- /dev/null +++ b/core/java/android/print/IPrintClient.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.IntentSender; + +/** + * Interface for communication with a printing app. + * + * @see android.print.IPrintClientCallback + * + * @hide + */ +oneway interface IPrintClient { + void startPrintJobConfigActivity(in IntentSender intent); +} diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b12c922cca6e3370aaf6e8d2353d6a3832d9dd90 --- /dev/null +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.print.ILayoutResultCallback; +import android.print.IWriteResultCallback; +import android.print.PageRange; +import android.print.PrintAttributes; + +/** + * Interface for communication with the print adapter object. + * + * @hide + */ +oneway interface IPrintDocumentAdapter { + void start(); + void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes, + ILayoutResultCallback callback, in Bundle metadata, int sequence); + void write(in PageRange[] pages, in ParcelFileDescriptor fd, + IWriteResultCallback callback, int sequence); + void finish(); +} diff --git a/core/java/android/print/IPrintJobStateChangeListener.aidl b/core/java/android/print/IPrintJobStateChangeListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c1d39f0f557d009f8ddc995ce5b407ff21a09d7d --- /dev/null +++ b/core/java/android/print/IPrintJobStateChangeListener.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.PrintJobId; + +/** + * Interface for observing print job state changes. + * + * @hide + */ +oneway interface IPrintJobStateChangeListener { + void onPrintJobStateChanged(in PrintJobId printJobId); +} diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8fa7ab90ebad5485b1261282f8fe5e93940d9b8a --- /dev/null +++ b/core/java/android/print/IPrintManager.aidl @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Bundle; +import android.print.IPrinterDiscoveryObserver; +import android.print.IPrintDocumentAdapter; +import android.print.PrintJobId; +import android.print.IPrintJobStateChangeListener; +import android.print.PrinterId; +import android.print.PrintJobInfo; +import android.print.PrintAttributes; +import android.printservice.PrintServiceInfo; + +/** + * Interface for communication with the core print manager service. + * + * @hide + */ +interface IPrintManager { + List getPrintJobInfos(int appId, int userId); + PrintJobInfo getPrintJobInfo(in PrintJobId printJobId, int appId, int userId); + Bundle print(String printJobName, in IPrintDocumentAdapter printAdapter, + in PrintAttributes attributes, String packageName, int appId, int userId); + void cancelPrintJob(in PrintJobId printJobId, int appId, int userId); + void restartPrintJob(in PrintJobId printJobId, int appId, int userId); + + void addPrintJobStateChangeListener(in IPrintJobStateChangeListener listener, + int appId, int userId); + void removePrintJobStateChangeListener(in IPrintJobStateChangeListener listener, + int userId); + + List getInstalledPrintServices(int userId); + List getEnabledPrintServices(int userId); + + void createPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId); + void startPrinterDiscovery(in IPrinterDiscoveryObserver observer, + in List priorityList, int userId); + void stopPrinterDiscovery(in IPrinterDiscoveryObserver observer, int userId); + void validatePrinters(in List 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/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b2cf253d56a12c48c648f877e7f1d0994d2d775 --- /dev/null +++ b/core/java/android/print/IPrintSpooler.aidl @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.ComponentName; +import android.os.ParcelFileDescriptor; +import android.print.IPrintSpoolerClient; +import android.print.IPrintSpoolerCallbacks; +import android.print.PrinterInfo; +import android.print.PrintAttributes; +import android.print.PrintJobId; +import android.print.PrintJobInfo; + +/** + * Interface for communication with the print spooler service. + * + * @see android.print.IPrintSpoolerCallbacks + * + * @hide + */ +oneway interface IPrintSpooler { + void removeObsoletePrintJobs(); + void getPrintJobInfos(IPrintSpoolerCallbacks callback, in ComponentName componentName, + int state, int appId, int sequence); + void getPrintJobInfo(in PrintJobId printJobId, IPrintSpoolerCallbacks callback, + int appId, int sequence); + void createPrintJob(in PrintJobInfo printJob); + void setPrintJobState(in PrintJobId printJobId, int status, String stateReason, + IPrintSpoolerCallbacks callback, int sequence); + void setPrintJobTag(in PrintJobId printJobId, String tag, IPrintSpoolerCallbacks callback, + int sequence); + void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId); + void setClient(IPrintSpoolerClient client); + void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling); +} diff --git a/core/java/android/print/IPrintSpoolerCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl new file mode 100644 index 0000000000000000000000000000000000000000..45c5332965dac7e8ad6c276a5d039b805c88448c --- /dev/null +++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.PrintJobInfo; +import java.util.List; + +/** + * Callbacks for communication with the print spooler service. + * + * @see android.print.IPrintSpoolerService + * + * @hide + */ +oneway interface IPrintSpoolerCallbacks { + void onGetPrintJobInfosResult(in List printJob, int sequence); + void onCancelPrintJobResult(boolean canceled, int sequence); + void onSetPrintJobStateResult(boolean success, int sequence); + void onSetPrintJobTagResult(boolean success, int sequence); + void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence); +} diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl new file mode 100644 index 0000000000000000000000000000000000000000..82708122738541d2fbfa4b2b214d637a374100f5 --- /dev/null +++ b/core/java/android/print/IPrintSpoolerClient.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.ComponentName; +import android.print.PrintJobInfo; +import android.print.PrintJobId; + +/** + * Interface for receiving interesting state updates from the print spooler. + * + * @hide + */ +oneway interface IPrintSpoolerClient { + void onPrintJobQueued(in PrintJobInfo printJob); + void onAllPrintJobsForServiceHandled(in ComponentName printService); + void onAllPrintJobsHandled(); + void onPrintJobStateChanged(in PrintJobInfo printJob); +} diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoveryObserver.aidl new file mode 100644 index 0000000000000000000000000000000000000000..2be7b6b4fcaaf63e7913362c6aac1fbc6015516c --- /dev/null +++ b/core/java/android/print/IPrinterDiscoveryObserver.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.content.pm.ParceledListSlice; + +/** + * Interface for observing discovered printers by a discovery session. + * + * @hide + */ +oneway interface IPrinterDiscoveryObserver { + void onPrintersAdded(in ParceledListSlice printers); + void onPrintersRemoved(in ParceledListSlice printerIds); +} diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8281c4e01c659fa72cf6155831d11ba2b29a07cc --- /dev/null +++ b/core/java/android/print/IWriteResultCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.PageRange; + +/** + * Callback for observing the result of android.print.DocuemntAdapter#onWrite. + * + * @hide + */ +oneway interface IWriteResultCallback { + void onWriteFinished(in PageRange[] pages, int sequence); + void onWriteFailed(CharSequence error, int sequence); +} diff --git a/core/java/android/print/PageRange.aidl b/core/java/android/print/PageRange.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b1ae14f73c2c44efd99d305aff055c0307db08f8 --- /dev/null +++ b/core/java/android/print/PageRange.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PageRange; diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java new file mode 100644 index 0000000000000000000000000000000000000000..cdcd0c7d2e6ab27a44a619fa7a6228fbf8808043 --- /dev/null +++ b/core/java/android/print/PageRange.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a range of pages. The start and end page indices of + * the range are zero based and inclusive. + */ +public final class PageRange implements Parcelable { + + /** + * Constant for specifying all pages. + */ + public static final PageRange ALL_PAGES = new PageRange(0, Integer.MAX_VALUE); + + private final int mStart; + private final int mEnd; + + /** + * Creates a new instance. + * + * @param start The start page index (zero based and inclusive). + * @param end The end page index (zero based and inclusive). + * + * @throws IllegalArgumentException If start is less than zero. + * @throws IllegalArgumentException If end is less than zero. + * @throws IllegalArgumentException If start greater than end. + */ + public PageRange(int start, int end) { + if (start < 0) { + throw new IllegalArgumentException("start cannot be less than zero."); + } + if (end < 0) { + throw new IllegalArgumentException("end cannot be less than zero."); + } + if (start > end) { + throw new IllegalArgumentException("start must be lesser than end."); + } + mStart = start; + mEnd = end; + } + + private PageRange (Parcel parcel) { + this(parcel.readInt(), parcel.readInt()); + } + + /** + * Gets the start page index (zero based and inclusive). + * + * @return The start page index. + */ + public int getStart() { + return mStart; + } + + /** + * Gets the end page index (zero based and inclusive). + * + * @return The end page index. + */ + public int getEnd() { + return mEnd; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mStart); + parcel.writeInt(mEnd); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mEnd; + result = prime * result + mStart; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PageRange other = (PageRange) obj; + if (mEnd != other.mEnd) { + return false; + } + if (mStart != other.mStart) { + return false; + } + return true; + } + + @Override + public String toString() { + if (mStart == 0 && mEnd == Integer.MAX_VALUE) { + return "PageRange[]"; + } + StringBuilder builder = new StringBuilder(); + builder.append("PageRange[") + .append(mStart) + .append(" - ") + .append(mEnd) + .append("]"); + return builder.toString(); + } + + public static final Parcelable.Creator CREATOR = + new Creator() { + @Override + public PageRange createFromParcel(Parcel parcel) { + return new PageRange(parcel); + } + + @Override + public PageRange[] newArray(int size) { + return new PageRange[size]; + } + }; +} diff --git a/core/java/android/print/PrintAttributes.aidl b/core/java/android/print/PrintAttributes.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0b765a20ffa3976dce75253ce5111f320380e4c6 --- /dev/null +++ b/core/java/android/print/PrintAttributes.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PrintAttributes; diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java new file mode 100644 index 0000000000000000000000000000000000000000..e1a9cb7592e5111c00ab624613190007a481d8b8 --- /dev/null +++ b/core/java/android/print/PrintAttributes.java @@ -0,0 +1,1255 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources.NotFoundException; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.R; + +import java.util.Map; + +/** + * This class represents the attributes of a print job. + */ +public final class PrintAttributes implements Parcelable { + /** Color mode: Monochrome color scheme, for example one color is used. */ + public static final int COLOR_MODE_MONOCHROME = 1 << 0; + /** Color mode: Color color scheme, for example many colors are used. */ + public static final int COLOR_MODE_COLOR = 1 << 1; + + private static final int VALID_COLOR_MODES = + COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR; + + private MediaSize mMediaSize; + private Resolution mResolution; + private Margins mMinMargins; + + private int mColorMode; + + PrintAttributes() { + /* hide constructor */ + } + + private PrintAttributes(Parcel parcel) { + mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null; + mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null; + mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; + mColorMode = parcel.readInt(); + } + + /** + * Gets the media size. + * + * @return The media size or null if not set. + */ + public MediaSize getMediaSize() { + return mMediaSize; + } + + /** + * Sets the media size. + * + * @param The media size. + * + * @hide + */ + public void setMediaSize(MediaSize mediaSize) { + mMediaSize = mediaSize; + } + + /** + * Gets the resolution. + * + * @return The resolution or null if not set. + */ + public Resolution getResolution() { + return mResolution; + } + + /** + * Sets the resolution. + * + * @param The resolution. + * + * @hide + */ + public void setResolution(Resolution resolution) { + mResolution = resolution; + } + + /** + * Gets the minimal margins. If the content does not fit + * these margins it will be clipped. + * + * @return The margins or null if not set. + */ + public Margins getMinMargins() { + return mMinMargins; + } + + /** + * Sets the minimal margins. If the content does not fit + * these margins it will be clipped. + * + * @param The margins. + * + * @hide + */ + public void setMinMargins(Margins margins) { + mMinMargins = margins; + } + + /** + * Gets the color mode. + * + * @return The color mode or zero if not set. + * + * @see #COLOR_MODE_COLOR + * @see #COLOR_MODE_MONOCHROME + */ + public int getColorMode() { + return mColorMode; + } + + /** + * Sets the color mode. + * + * @param The color mode. + * + * @see #COLOR_MODE_MONOCHROME + * @see #COLOR_MODE_COLOR + * + * @hide + */ + public void setColorMode(int colorMode) { + enforceValidColorMode(colorMode); + mColorMode = colorMode; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + if (mMediaSize != null) { + parcel.writeInt(1); + mMediaSize.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + if (mResolution != null) { + parcel.writeInt(1); + mResolution.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + if (mMinMargins != null) { + parcel.writeInt(1); + mMinMargins.writeToParcel(parcel); + } else { + parcel.writeInt(0); + } + parcel.writeInt(mColorMode); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mColorMode; + result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode()); + result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode()); + result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintAttributes other = (PrintAttributes) obj; + if (mColorMode != other.mColorMode) { + return false; + } + if (mMinMargins == null) { + if (other.mMinMargins != null) { + return false; + } + } else if (!mMinMargins.equals(other.mMinMargins)) { + return false; + } + if (mMediaSize == null) { + if (other.mMediaSize != null) { + return false; + } + } else if (!mMediaSize.equals(other.mMediaSize)) { + return false; + } + if (mResolution == null) { + if (other.mResolution != null) { + return false; + } + } else if (!mResolution.equals(other.mResolution)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrintAttributes{"); + builder.append("mediaSize: ").append(mMediaSize); + if (mMediaSize != null) { + builder.append(", orientation: ").append(mMediaSize.isPortrait() + ? "portrait" : "landscape"); + } else { + builder.append(", orientation: ").append("null"); + } + builder.append(", resolution: ").append(mResolution); + builder.append(", minMargins: ").append(mMinMargins); + builder.append(", colorMode: ").append(colorModeToString(mColorMode)); + builder.append("}"); + return builder.toString(); + } + + /** @hide */ + public void clear() { + mMediaSize = null; + mResolution = null; + mMinMargins = null; + mColorMode = 0; + } + + /** + * @hide + */ + public void copyFrom(PrintAttributes other) { + mMediaSize = other.mMediaSize; + mResolution = other.mResolution; + mMinMargins = other.mMinMargins; + mColorMode = other.mColorMode; + } + + /** + * This class specifies a supported media size. Media size is the + * dimension of the media on which the content is printed. For + * example, the {@link #NA_LETTER} media size designates a page + * with size 8.5" x 11". + */ + public static final class MediaSize { + private static final String LOG_TAG = "MediaSize"; + + private static final Map sIdToMediaSizeMap = + new ArrayMap(); + + /** + * Unknown media size in portrait mode. + *

    + * Note: This is for specifying orientation without media + * size. You should not use the dimensions reported by this class. + *

    + */ + public static final MediaSize UNKNOWN_PORTRAIT = + new MediaSize("UNKNOWN_PORTRAIT", "android", + R.string.mediasize_unknown_portrait, 1, Integer.MAX_VALUE); + + /** + * Unknown media size in landscape mode. + *

    + * Note: This is for specifying orientation without media + * size. You should not use the dimensions reported by this class. + *

    + */ + public static final MediaSize UNKNOWN_LANDSCAPE = + new MediaSize("UNKNOWN_LANDSCAPE", "android", + R.string.mediasize_unknown_landscape, Integer.MAX_VALUE, 1); + + // ISO sizes + + /** ISO A0 media size: 841mm x 1189mm (33.11" x 46.81") */ + public static final MediaSize ISO_A0 = + new MediaSize("ISO_A0", "android", R.string.mediasize_iso_a0, 33110, 46810); + /** ISO A1 media size: 594mm x 841mm (23.39" x 33.11") */ + public static final MediaSize ISO_A1 = + new MediaSize("ISO_A1", "android", R.string.mediasize_iso_a1, 23390, 33110); + /** ISO A2 media size: 420mm x 594mm (16.54" x 23.39") */ + public static final MediaSize ISO_A2 = + new MediaSize("ISO_A2", "android", R.string.mediasize_iso_a2, 16540, 23390); + /** ISO A3 media size: 297mm x 420mm (11.69" x 16.54") */ + public static final MediaSize ISO_A3 = + new MediaSize("ISO_A3", "android", R.string.mediasize_iso_a3, 11690, 16540); + /** ISO A4 media size: 210mm x 297mm (8.27" x 11.69") */ + public static final MediaSize ISO_A4 = + new MediaSize("ISO_A4", "android", R.string.mediasize_iso_a4, 8270, 11690); + /** ISO A5 media size: 148mm x 210mm (5.83" x 8.27") */ + public static final MediaSize ISO_A5 = + new MediaSize("ISO_A5", "android", R.string.mediasize_iso_a5, 5830, 8270); + /** ISO A6 media size: 105mm x 148mm (4.13" x 5.83") */ + public static final MediaSize ISO_A6 = + new MediaSize("ISO_A6", "android", R.string.mediasize_iso_a6, 4130, 5830); + /** ISO A7 media size: 74mm x 105mm (2.91" x 4.13") */ + public static final MediaSize ISO_A7 = + new MediaSize("ISO_A7", "android", R.string.mediasize_iso_a7, 2910, 4130); + /** ISO A8 media size: 52mm x 74mm (2.05" x 2.91") */ + public static final MediaSize ISO_A8 = + new MediaSize("ISO_A8", "android", R.string.mediasize_iso_a8, 2050, 2910); + /** ISO A9 media size: 37mm x 52mm (1.46" x 2.05") */ + public static final MediaSize ISO_A9 = + new MediaSize("ISO_A9", "android", R.string.mediasize_iso_a9, 1460, 2050); + /** ISO A10 media size: 26mm x 37mm (1.02" x 1.46") */ + public static final MediaSize ISO_A10 = + new MediaSize("ISO_A10", "android", R.string.mediasize_iso_a10, 1020, 1460); + + /** ISO B0 media size: 1000mm x 1414mm (39.37" x 55.67") */ + public static final MediaSize ISO_B0 = + new MediaSize("ISO_B0", "android", R.string.mediasize_iso_b0, 39370, 55670); + /** ISO B1 media size: 707mm x 1000mm (27.83" x 39.37") */ + public static final MediaSize ISO_B1 = + new MediaSize("ISO_B1", "android", R.string.mediasize_iso_b1, 27830, 39370); + /** ISO B2 media size: 500mm x 707mm (19.69" x 27.83") */ + public static final MediaSize ISO_B2 = + new MediaSize("ISO_B2", "android", R.string.mediasize_iso_b2, 19690, 27830); + /** ISO B3 media size: 353mm x 500mm (13.90" x 19.69") */ + public static final MediaSize ISO_B3 = + new MediaSize("ISO_B3", "android", R.string.mediasize_iso_b3, 13900, 19690); + /** ISO B4 media size: 250mm x 353mm (9.84" x 13.90") */ + public static final MediaSize ISO_B4 = + new MediaSize("ISO_B4", "android", R.string.mediasize_iso_b4, 9840, 13900); + /** ISO B5 media size: 176mm x 250mm (6.93" x 9.84") */ + public static final MediaSize ISO_B5 = + new MediaSize("ISO_B5", "android", R.string.mediasize_iso_b5, 6930, 9840); + /** ISO B6 media size: 125mm x 176mm (4.92" x 6.93") */ + public static final MediaSize ISO_B6 = + new MediaSize("ISO_B6", "android", R.string.mediasize_iso_b6, 4920, 6930); + /** ISO B7 media size: 88mm x 125mm (3.46" x 4.92") */ + public static final MediaSize ISO_B7 = + new MediaSize("ISO_B7", "android", R.string.mediasize_iso_b7, 3460, 4920); + /** ISO B8 media size: 62mm x 88mm (2.44" x 3.46") */ + public static final MediaSize ISO_B8 = + new MediaSize("ISO_B8", "android", R.string.mediasize_iso_b8, 2440, 3460); + /** ISO B9 media size: 44mm x 62mm (1.73" x 2.44") */ + public static final MediaSize ISO_B9 = + new MediaSize("ISO_B9", "android", R.string.mediasize_iso_b9, 1730, 2440); + /** ISO B10 media size: 31mm x 44mm (1.22" x 1.73") */ + public static final MediaSize ISO_B10 = + new MediaSize("ISO_B10", "android", R.string.mediasize_iso_b10, 1220, 1730); + + /** ISO C0 media size: 917mm x 1297mm (36.10" x 51.06") */ + public static final MediaSize ISO_C0 = + new MediaSize("ISO_C0", "android", R.string.mediasize_iso_c0, 36100, 51060); + /** ISO C1 media size: 648mm x 917mm (25.51" x 36.10") */ + public static final MediaSize ISO_C1 = + new MediaSize("ISO_C1", "android", R.string.mediasize_iso_c1, 25510, 36100); + /** ISO C2 media size: 458mm x 648mm (18.03" x 25.51") */ + public static final MediaSize ISO_C2 = + new MediaSize("ISO_C2", "android", R.string.mediasize_iso_c2, 18030, 25510); + /** ISO C3 media size: 324mm x 458mm (12.76" x 18.03") */ + public static final MediaSize ISO_C3 = + new MediaSize("ISO_C3", "android", R.string.mediasize_iso_c3, 12760, 18030); + /** ISO C4 media size: 229mm x 324mm (9.02" x 12.76") */ + public static final MediaSize ISO_C4 = + new MediaSize("ISO_C4", "android", R.string.mediasize_iso_c4, 9020, 12760); + /** ISO C5 media size: 162mm x 229mm (6.38" x 9.02") */ + public static final MediaSize ISO_C5 = + new MediaSize("ISO_C5", "android", R.string.mediasize_iso_c5, 6380, 9020); + /** ISO C6 media size: 114mm x 162mm (4.49" x 6.38") */ + public static final MediaSize ISO_C6 = + new MediaSize("ISO_C6", "android", R.string.mediasize_iso_c6, 4490, 6380); + /** ISO C7 media size: 81mm x 114mm (3.19" x 4.49") */ + public static final MediaSize ISO_C7 = + new MediaSize("ISO_C7", "android", R.string.mediasize_iso_c7, 3190, 4490); + /** ISO C8 media size: 57mm x 81mm (2.24" x 3.19") */ + public static final MediaSize ISO_C8 = + new MediaSize("ISO_C8", "android", R.string.mediasize_iso_c8, 2240, 3190); + /** ISO C9 media size: 40mm x 57mm (1.57" x 2.24") */ + public static final MediaSize ISO_C9 = + new MediaSize("ISO_C9", "android", R.string.mediasize_iso_c9, 1570, 2240); + /** ISO C10 media size: 28mm x 40mm (1.10" x 1.57") */ + public static final MediaSize ISO_C10 = + new MediaSize("ISO_C10", "android", R.string.mediasize_iso_c10, 1100, 1570); + + // North America + + /** North America Letter media size: 8.5" x 11" (279mm x 216mm) */ + public static final MediaSize NA_LETTER = + new MediaSize("NA_LETTER", "android", R.string.mediasize_na_letter, 8500, 11000); + /** North America Government-Letter media size: 8.0" x 10.5" (203mm x 267mm) */ + public static final MediaSize NA_GOVT_LETTER = + new MediaSize("NA_GOVT_LETTER", "android", + R.string.mediasize_na_gvrnmt_letter, 8000, 10500); + /** North America Legal media size: 8.5" x 14" (216mm x 356mm) */ + public static final MediaSize NA_LEGAL = + new MediaSize("NA_LEGAL", "android", R.string.mediasize_na_legal, 8500, 14000); + /** North America Junior Legal media size: 8.0" x 5.0" (203mm × 127mm) */ + public static final MediaSize NA_JUNIOR_LEGAL = + new MediaSize("NA_JUNIOR_LEGAL", "android", + R.string.mediasize_na_junior_legal, 8000, 5000); + /** North America Ledger media size: 17" x 11" (432mm × 279mm) */ + public static final MediaSize NA_LEDGER = + new MediaSize("NA_LEDGER", "android", R.string.mediasize_na_ledger, 17000, 11000); + /** North America Tabloid media size: 11" x 17" (279mm × 432mm) */ + public static final MediaSize NA_TABLOID = + new MediaSize("NA_TABLOID", "android", + R.string.mediasize_na_tabloid, 11000, 17000); + /** North America Index Card 3x5 media size: 3" x 5" (76mm x 127mm) */ + public static final MediaSize NA_INDEX_3X5 = + new MediaSize("NA_INDEX_3X5", "android", + R.string.mediasize_na_index_3x5, 3000, 5000); + /** North America Index Card 4x6 media size: 4" x 6" (102mm x 152mm) */ + public static final MediaSize NA_INDEX_4X6 = + new MediaSize("NA_INDEX_4X6", "android", + R.string.mediasize_na_index_4x6, 4000, 6000); + /** North America Index Card 5x8 media size: 5" x 8" (127mm x 203mm) */ + public static final MediaSize NA_INDEX_5X8 = + new MediaSize("NA_INDEX_5X8", "android", + R.string.mediasize_na_index_5x8, 5000, 8000); + /** North America Monarch media size: 7.25" x 10.5" (184mm x 267mm) */ + public static final MediaSize NA_MONARCH = + new MediaSize("NA_MONARCH", "android", + R.string.mediasize_na_monarch, 7250, 10500); + /** North America Quarto media size: 8" x 10" (203mm x 254mm) */ + public static final MediaSize NA_QUARTO = + new MediaSize("NA_QUARTO", "android", + R.string.mediasize_na_quarto, 8000, 10000); + /** North America Foolscap media size: 8" x 13" (203mm x 330mm) */ + public static final MediaSize NA_FOOLSCAP = + new MediaSize("NA_FOOLSCAP", "android", + R.string.mediasize_na_foolscap, 8000, 13000); + + // Chinese + + /** Chinese ROC 8K media size: 270mm x 390mm (10.629" x 15.3543") */ + public static final MediaSize ROC_8K = + new MediaSize("ROC_8K", "android", + R.string.mediasize_chinese_roc_8k, 10629, 15354); + /** Chinese ROC 16K media size: 195mm x 270mm (7.677" x 10.629") */ + public static final MediaSize ROC_16K = + new MediaSize("ROC_16K", "android", + R.string.mediasize_chinese_roc_16k, 7677, 10629); + + /** Chinese PRC 1 media size: 102mm x 165mm (4.015" x 6.496") */ + public static final MediaSize PRC_1 = + new MediaSize("PRC_1", "android", + R.string.mediasize_chinese_prc_1, 4015, 6496); + /** Chinese PRC 2 media size: 102mm x 176mm (4.015" x 6.929") */ + public static final MediaSize PRC_2 = + new MediaSize("PRC_2", "android", + R.string.mediasize_chinese_prc_2, 4015, 6929); + /** Chinese PRC 3 media size: 125mm x 176mm (4.921" x 6.929") */ + public static final MediaSize PRC_3 = + new MediaSize("PRC_3", "android", + R.string.mediasize_chinese_prc_3, 4921, 6929); + /** Chinese PRC 4 media size: 110mm x 208mm (4.330" x 8.189") */ + public static final MediaSize PRC_4 = + new MediaSize("PRC_4", "android", + R.string.mediasize_chinese_prc_4, 4330, 8189); + /** Chinese PRC 5 media size: 110mm x 220mm (4.330" x 8.661") */ + public static final MediaSize PRC_5 = + new MediaSize("PRC_5", "android", + R.string.mediasize_chinese_prc_5, 4330, 8661); + /** Chinese PRC 6 media size: 120mm x 320mm (4.724" x 12.599") */ + public static final MediaSize PRC_6 = + new MediaSize("PRC_6", "android", + R.string.mediasize_chinese_prc_6, 4724, 12599); + /** Chinese PRC 7 media size: 160mm x 230mm (6.299" x 9.055") */ + public static final MediaSize PRC_7 = + new MediaSize("PRC_7", "android", + R.string.mediasize_chinese_prc_7, 6299, 9055); + /** Chinese PRC 8 media size: 120mm x 309mm (4.724" x 12.165") */ + public static final MediaSize PRC_8 = + new MediaSize("PRC_8", "android", + R.string.mediasize_chinese_prc_8, 4724, 12165); + /** Chinese PRC 9 media size: 229mm x 324mm (9.016" x 12.756") */ + public static final MediaSize PRC_9 = + new MediaSize("PRC_9", "android", + R.string.mediasize_chinese_prc_9, 9016, 12756); + /** Chinese PRC 10 media size: 324mm x 458mm (12.756" x 18.032") */ + public static final MediaSize PRC_10 = + new MediaSize("PRC_10", "android", + R.string.mediasize_chinese_prc_10, 12756, 18032); + + /** Chinese PRC 16k media size: 146mm x 215mm (5.749" x 8.465") */ + public static final MediaSize PRC_16K = + new MediaSize("PRC_16K", "android", + R.string.mediasize_chinese_prc_16k, 5749, 8465); + /** Chinese Pa Kai media size: 267mm x 389mm (10.512" x 15.315") */ + public static final MediaSize OM_PA_KAI = + new MediaSize("OM_PA_KAI", "android", + R.string.mediasize_chinese_om_pa_kai, 10512, 15315); + /** Chinese Dai Pa Kai media size: 275mm x 395mm (10.827" x 15.551") */ + public static final MediaSize OM_DAI_PA_KAI = + new MediaSize("OM_DAI_PA_KAI", "android", + R.string.mediasize_chinese_om_dai_pa_kai, 10827, 15551); + /** Chinese Jurro Ku Kai media size: 198mm x 275mm (7.796" x 10.827") */ + public static final MediaSize OM_JUURO_KU_KAI = + new MediaSize("OM_JUURO_KU_KAI", "android", + R.string.mediasize_chinese_om_jurro_ku_kai, 7796, 10827); + + // Japanese + + /** Japanese JIS B10 media size: 32mm x 45mm (1.259" x 1.772") */ + public static final MediaSize JIS_B10 = + new MediaSize("JIS_B10", "android", + R.string.mediasize_japanese_jis_b10, 1259, 1772); + /** Japanese JIS B9 media size: 45mm x 64mm (1.772" x 2.52") */ + public static final MediaSize JIS_B9 = + new MediaSize("JIS_B9", "android", + R.string.mediasize_japanese_jis_b9, 1772, 2520); + /** Japanese JIS B8 media size: 64mm x 91mm (2.52" x 3.583") */ + public static final MediaSize JIS_B8 = + new MediaSize("JIS_B8", "android", + R.string.mediasize_japanese_jis_b8, 2520, 3583); + /** Japanese JIS B7 media size: 91mm x 128mm (3.583" x 5.049") */ + public static final MediaSize JIS_B7 = + new MediaSize("JIS_B7", "android", + R.string.mediasize_japanese_jis_b7, 3583, 5049); + /** Japanese JIS B6 media size: 128mm x 182mm (5.049" x 7.165") */ + public static final MediaSize JIS_B6 = + new MediaSize("JIS_B6", "android", + R.string.mediasize_japanese_jis_b6, 5049, 7165); + /** Japanese JIS B5 media size: 182mm x 257mm (7.165" x 10.118") */ + public static final MediaSize JIS_B5 = + new MediaSize("JIS_B5", "android", + R.string.mediasize_japanese_jis_b5, 7165, 10118); + /** Japanese JIS B4 media size: 257mm x 364mm (10.118" x 14.331") */ + public static final MediaSize JIS_B4 = + new MediaSize("JIS_B4", "android", + R.string.mediasize_japanese_jis_b4, 10118, 14331); + /** Japanese JIS B3 media size: 364mm x 515mm (14.331" x 20.276") */ + public static final MediaSize JIS_B3 = + new MediaSize("JIS_B3", "android", + R.string.mediasize_japanese_jis_b3, 14331, 20276); + /** Japanese JIS B2 media size: 515mm x 728mm (20.276" x 28.661") */ + public static final MediaSize JIS_B2 = + new MediaSize("JIS_B2", "android", + R.string.mediasize_japanese_jis_b2, 20276, 28661); + /** Japanese JIS B1 media size: 728mm x 1030mm (28.661" x 40.551") */ + public static final MediaSize JIS_B1 = + new MediaSize("JIS_B1", "android", + R.string.mediasize_japanese_jis_b1, 28661, 40551); + /** Japanese JIS B0 media size: 1030mm x 1456mm (40.551" x 57.323") */ + public static final MediaSize JIS_B0 = + new MediaSize("JIS_B0", "android", + R.string.mediasize_japanese_jis_b0, 40551, 57323); + + /** Japanese JIS Exec media size: 216mm x 330mm (8.504" x 12.992") */ + public static final MediaSize JIS_EXEC = + new MediaSize("JIS_EXEC", "android", + R.string.mediasize_japanese_jis_exec, 8504, 12992); + + /** Japanese Chou4 media size: 90mm x 205mm (3.543" x 8.071") */ + public static final MediaSize JPN_CHOU4 = + new MediaSize("JPN_CHOU4", "android", + R.string.mediasize_japanese_chou4, 3543, 8071); + /** Japanese Chou3 media size: 120mm x 235mm (4.724" x 9.252") */ + public static final MediaSize JPN_CHOU3 = + new MediaSize("JPN_CHOU3", "android", + R.string.mediasize_japanese_chou3, 4724, 9252); + /** Japanese Chou2 media size: 111.1mm x 146mm (4.374" x 5.748") */ + public static final MediaSize JPN_CHOU2 = + new MediaSize("JPN_CHOU2", "android", + R.string.mediasize_japanese_chou2, 4374, 5748); + + /** Japanese Hagaki media size: 100mm x 148mm (3.937" x 5.827") */ + public static final MediaSize JPN_HAGAKI = + new MediaSize("JPN_HAGAKI", "android", + R.string.mediasize_japanese_hagaki, 3937, 5827); + /** Japanese Oufuku media size: 148mm x 200mm (5.827" x 7.874") */ + public static final MediaSize JPN_OUFUKU = + new MediaSize("JPN_OUFUKU", "android", + R.string.mediasize_japanese_oufuku, 5827, 7874); + + /** Japanese Kahu media size: 240mm x 322.1mm (9.449" x 12.681") */ + public static final MediaSize JPN_KAHU = + new MediaSize("JPN_KAHU", "android", + R.string.mediasize_japanese_kahu, 9449, 12681); + /** Japanese Kaku2 media size: 240mm x 332mm (9.449" x 13.071") */ + public static final MediaSize JPN_KAKU2 = + new MediaSize("JPN_KAKU2", "android", + R.string.mediasize_japanese_kaku2, 9449, 13071); + + /** Japanese You4 media size: 105mm x 235mm (4.134" x 9.252") */ + public static final MediaSize JPN_YOU4 = + new MediaSize("JPN_YOU4", "android", + R.string.mediasize_japanese_you4, 4134, 9252); + + private final String mId; + /**@hide */ + public final String mLabel; + /**@hide */ + public final String mPackageName; + /**@hide */ + public final int mLabelResId; + private final int mWidthMils; + private final int mHeightMils; + + /** + * Creates a new instance. This is the preferred constructor since + * it enables the media size label to be shown in a localized fashion + * on a locale change. + * + * @param id The unique media size id. + * @param packageName The name of the creating package. + * @param labelResId The resource if of a human readable label. + * @param widthMils The width in mils (thousands of an inch). + * @param heightMils The height in mils (thousands of an inch). + * + * @throws IllegalArgumentException If the id is empty. + * @throws IllegalArgumentException If the label is empty. + * @throws IllegalArgumentException If the widthMils is less than or equal to zero. + * @throws IllegalArgumentException If the heightMils is less than or equal to zero. + * + * @hide + */ + public MediaSize(String id, String packageName, int labelResId, + int widthMils, int heightMils) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id cannot be empty."); + } + if (TextUtils.isEmpty(packageName)) { + throw new IllegalArgumentException("packageName cannot be empty."); + } + if (labelResId <= 0) { + throw new IllegalArgumentException("labelResId must be greater than zero."); + } + if (widthMils <= 0) { + throw new IllegalArgumentException("widthMils " + + "cannot be less than or equal to zero."); + } + if (heightMils <= 0) { + throw new IllegalArgumentException("heightMils " + + "cannot be less than or euqual to zero."); + } + mPackageName = packageName; + mId = id; + mLabelResId = labelResId; + mWidthMils = widthMils; + mHeightMils = heightMils; + mLabel = null; + + // Build this mapping only for predefined media sizes. + sIdToMediaSizeMap.put(mId, this); + } + + /** + * Creates a new instance. + * + * @param id The unique media size id. It is unique amongst other media sizes + * supported by the printer. + * @param label The internationalized human readable label. + * @param widthMils The width in mils (thousands of an inch). + * @param heightMils The height in mils (thousands of an inch). + * + * @throws IllegalArgumentException If the id is empty. + * @throws IllegalArgumentException If the label is empty. + * @throws IllegalArgumentException If the widthMils is less than or equal to zero. + * @throws IllegalArgumentException If the heightMils is less than or equal to zero. + */ + public MediaSize(String id, String label, int widthMils, int heightMils) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id cannot be empty."); + } + if (TextUtils.isEmpty(label)) { + throw new IllegalArgumentException("label cannot be empty."); + } + if (widthMils <= 0) { + throw new IllegalArgumentException("widthMils " + + "cannot be less than or equal to zero."); + } + if (heightMils <= 0) { + throw new IllegalArgumentException("heightMils " + + "cannot be less than or euqual to zero."); + } + mId = id; + mLabel = label; + mWidthMils = widthMils; + mHeightMils = heightMils; + mLabelResId = 0; + mPackageName = null; + } + + /** @hide */ + public MediaSize(String id, String label, String packageName, + int widthMils, int heightMils, int labelResId) { + mPackageName = packageName; + mId = id; + mLabelResId = labelResId; + mWidthMils = widthMils; + mHeightMils = heightMils; + mLabel = label; + } + + /** + * Gets the unique media size id. It is unique amongst other media sizes + * supported by the printer. + *

    + * This id is defined by the client that generated the media size + * instance and should not be interpreted by other parties. + *

    + * + * @return The unique media size id. + */ + public String getId() { + return mId; + } + + /** + * Gets the human readable media size label. + * + * @param packageManager The package manager for loading the label. + * @return The human readable label. + */ + public String getLabel(PackageManager packageManager) { + if (!TextUtils.isEmpty(mPackageName) && mLabelResId > 0) { + try { + return packageManager.getResourcesForApplication( + mPackageName).getString(mLabelResId); + } catch (NotFoundException nfe) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } catch (NameNotFoundException nnfee) { + Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + + " from package " + mPackageName); + } + } + return mLabel; + } + + /** + * Gets the media width in mils (thousands of an inch). + * + * @return The media width. + */ + public int getWidthMils() { + return mWidthMils; + } + + /** + * Gets the media height in mils (thousands of an inch). + * + * @return The media height. + */ + public int getHeightMils() { + return mHeightMils; + } + + /** + * Gets whether this media size is in portrait which is the + * height is greater or equal to the width. + * + * @return True if the media size is in portrait, false if + * it is in landscape. + */ + public boolean isPortrait() { + return mHeightMils >= mWidthMils; + } + + /** + * Returns a new media size in a portrait orientation + * which is the height is the greater dimension. + * + * @return New instance in landscape orientation. + */ + public MediaSize asPortrait() { + return new MediaSize(mId, mLabel, mPackageName, + Math.min(mWidthMils, mHeightMils), + Math.max(mWidthMils, mHeightMils), + mLabelResId); + } + + /** + * Returns a new media size in a landscape orientation + * which is the height is the lesser dimension. + * + * @return New instance in landscape orientation. + */ + public MediaSize asLandscape() { + return new MediaSize(mId, mLabel, mPackageName, + Math.max(mWidthMils, mHeightMils), + Math.min(mWidthMils, mHeightMils), + mLabelResId); + } + + void writeToParcel(Parcel parcel) { + parcel.writeString(mId); + parcel.writeString(mLabel); + parcel.writeString(mPackageName); + parcel.writeInt(mWidthMils); + parcel.writeInt(mHeightMils); + parcel.writeInt(mLabelResId); + } + + static MediaSize createFromParcel(Parcel parcel) { + return new MediaSize( + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mWidthMils; + result = prime * result + mHeightMils; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MediaSize other = (MediaSize) obj; + if (mWidthMils != other.mWidthMils) { + return false; + } + if (mHeightMils != other.mHeightMils) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MediaSize{"); + builder.append("id: ").append(mId); + builder.append(", label: ").append(mLabel); + builder.append(", packageName: ").append(mPackageName); + builder.append(", heightMils: ").append(mHeightMils); + builder.append(", widthMils: ").append(mWidthMils); + builder.append(", labelResId: ").append(mLabelResId); + builder.append("}"); + return builder.toString(); + } + + /** + * Gets a standard media size given its id. + * + * @param id The media size id. + * @return The media size for the given id or null. + * + * @hide + */ + public static MediaSize getStandardMediaSizeById(String id) { + return sIdToMediaSizeMap.get(id); + } + } + + /** + * This class specifies a supported resolution in DPI (dots per inch). + * Resolution defines how many points with different color can be placed + * on one inch in horizontal or vertical direction of the target media. + * For example, a printer with 600DIP can produce higher quality images + * the one with 300DPI resolution. + */ + public static final class Resolution { + private final String mId; + private final String mLabel; + private final int mHorizontalDpi; + private final int mVerticalDpi; + + /** + * Creates a new instance. + * + * @param id The unique resolution id. It is unique amongst other resolutions + * supported by the printer. + * @param label The internationalized human readable label. + * @param horizontalDpi The horizontal resolution in DPI (dots per inch). + * @param verticalDpi The vertical resolution in DPI (dots per inch). + * + * @throws IllegalArgumentException If the id is empty. + * @throws IllegalArgumentException If the label is empty. + * @throws IllegalArgumentException If the horizontalDpi is less than or equal to zero. + * @throws IllegalArgumentException If the verticalDpi is less than or equal to zero. + */ + public Resolution(String id, String label, int horizontalDpi, int verticalDpi) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id cannot be empty."); + } + if (TextUtils.isEmpty(label)) { + throw new IllegalArgumentException("label cannot be empty."); + } + if (horizontalDpi <= 0) { + throw new IllegalArgumentException("horizontalDpi " + + "cannot be less than or equal to zero."); + } + if (verticalDpi <= 0) { + throw new IllegalArgumentException("verticalDpi" + + " cannot be less than or equal to zero."); + } + mId = id; + mLabel = label; + mHorizontalDpi = horizontalDpi; + mVerticalDpi = verticalDpi; + } + + /** + * Gets the unique resolution id. It is unique amongst other resolutions + * supported by the printer. + *

    + * This id is defined by the client that generated the resolution + * instance and should not be interpreted by other parties. + *

    + * + * @return The unique resolution id. + */ + public String getId() { + return mId; + } + + /** + * Gets the resolution human readable label. + * + * @return The human readable label. + */ + public String getLabel() { + return mLabel; + } + + /** + * Gets the horizontal resolution in DPI (dots per inch). + * + * @return The horizontal resolution. + */ + public int getHorizontalDpi() { + return mHorizontalDpi; + } + + /** + * Gets the vertical resolution in DPI (dots per inch). + * + * @return The vertical resolution. + */ + public int getVerticalDpi() { + return mVerticalDpi; + } + + void writeToParcel(Parcel parcel) { + parcel.writeString(mId); + parcel.writeString(mLabel); + parcel.writeInt(mHorizontalDpi); + parcel.writeInt(mVerticalDpi); + } + + static Resolution createFromParcel(Parcel parcel) { + return new Resolution( + parcel.readString(), + parcel.readString(), + parcel.readInt(), + parcel.readInt()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mHorizontalDpi; + result = prime * result + mVerticalDpi; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Resolution other = (Resolution) obj; + if (mHorizontalDpi != other.mHorizontalDpi) { + return false; + } + if (mVerticalDpi != other.mVerticalDpi) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Resolution{"); + builder.append("id: ").append(mId); + builder.append(", label: ").append(mLabel); + builder.append(", horizontalDpi: ").append(mHorizontalDpi); + builder.append(", verticalDpi: ").append(mVerticalDpi); + builder.append("}"); + return builder.toString(); + } + } + + /** + * This class specifies content margins. Margins define the white space + * around the content where the left margin defines the amount of white + * space on the left of the content and so on. + */ + 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; + private final int mBottomMils; + + /** + * Creates a new instance. + * + * @param leftMils The left margin in mils (thousands of an inch). + * @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). + */ + public Margins(int leftMils, int topMils, int rightMils, int bottomMils) { + mTopMils = topMils; + mLeftMils = leftMils; + mRightMils = rightMils; + mBottomMils = bottomMils; + } + + /** + * Gets the left margin in mils (thousands of an inch). + * + * @return The left margin. + */ + public int getLeftMils() { + return mLeftMils; + } + + /** + * Gets the top margin in mils (thousands of an inch). + * + * @return The top margin. + */ + public int getTopMils() { + return mTopMils; + } + + /** + * Gets the right margin in mils (thousands of an inch). + * + * @return The right margin. + */ + public int getRightMils() { + return mRightMils; + } + + /** + * Gets the bottom margin in mils (thousands of an inch). + * + * @return The bottom margin. + */ + public int getBottomMils() { + return mBottomMils; + } + + void writeToParcel(Parcel parcel) { + parcel.writeInt(mLeftMils); + parcel.writeInt(mTopMils); + parcel.writeInt(mRightMils); + parcel.writeInt(mBottomMils); + } + + static Margins createFromParcel(Parcel parcel) { + return new Margins( + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mBottomMils; + result = prime * result + mLeftMils; + result = prime * result + mRightMils; + result = prime * result + mTopMils; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Margins other = (Margins) obj; + if (mBottomMils != other.mBottomMils) { + return false; + } + if (mLeftMils != other.mLeftMils) { + return false; + } + if (mRightMils != other.mRightMils) { + return false; + } + if (mTopMils != other.mTopMils) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Margins{"); + builder.append("leftMils: ").append(mLeftMils); + builder.append(", topMils: ").append(mTopMils); + builder.append(", rightMils: ").append(mRightMils); + builder.append(", bottomMils: ").append(mBottomMils); + builder.append("}"); + return builder.toString(); + } + } + + static String colorModeToString(int colorMode) { + switch (colorMode) { + case COLOR_MODE_MONOCHROME: { + return "COLOR_MODE_MONOCHROME"; + } + case COLOR_MODE_COLOR: { + return "COLOR_MODE_COLOR"; + } + default: + return "COLOR_MODE_UNKNOWN"; + } + } + + static void enforceValidColorMode(int colorMode) { + if ((colorMode & VALID_COLOR_MODES) == 0 && Integer.bitCount(colorMode) == 1) { + throw new IllegalArgumentException("invalid color mode: " + colorMode); + } + } + + /** + * Builder for creating {@link PrintAttributes}. + */ + public static final class Builder { + private final PrintAttributes mAttributes = new PrintAttributes(); + + /** + * Sets the media size. + * + * @param mediaSize The media size. + * @return This builder. + */ + public Builder setMediaSize(MediaSize mediaSize) { + mAttributes.setMediaSize(mediaSize); + return this; + } + + /** + * Sets the resolution. + * + * @param resolution The resolution. + * @return This builder. + */ + public Builder setResolution(Resolution resolution) { + mAttributes.setResolution(resolution); + return this; + } + + /** + * Sets the minimal margins. If the content does not fit + * these margins it will be clipped. + * + * @param margins The margins. + * @return This builder. + */ + public Builder setMinMargins(Margins margins) { + mAttributes.setMinMargins(margins); + return this; + } + + /** + * Sets the color mode. + * + * @param colorMode A valid color mode or zero. + * @return This builder. + * + * @see PrintAttributes#COLOR_MODE_MONOCHROME + * @see PrintAttributes#COLOR_MODE_COLOR + */ + public Builder setColorMode(int colorMode) { + if (Integer.bitCount(colorMode) > 1) { + throw new IllegalArgumentException("can specify at most one colorMode bit."); + } + mAttributes.setColorMode(colorMode); + return this; + } + + /** + * Creates a new {@link PrintAttributes} instance. + * + * @return The new instance. + */ + public PrintAttributes build() { + return mAttributes; + } + } + + public static final Parcelable.Creator CREATOR = + new Creator() { + @Override + public PrintAttributes createFromParcel(Parcel parcel) { + return new PrintAttributes(parcel); + } + + @Override + public PrintAttributes[] newArray(int size) { + return new PrintAttributes[size]; + } + }; +} diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..4113ac737c1a43aa3349fd89379618b5b6a36cd2 --- /dev/null +++ b/core/java/android/print/PrintDocumentAdapter.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; + +/** + * Base class that provides the content of a document to be printed. + * + *

    Lifecycle

    + *

    + *

      + *
    • + * Initially, you will receive a call to {@link #onStart()}. This callback + * can be used to allocate resources. + *
    • + *
    • + * Next, you will get one or more calls to {@link #onLayout(PrintAttributes, + * PrintAttributes, CancellationSignal, LayoutResultCallback, Bundle)} to + * inform you that the print attributes (page size, density, etc) changed + * giving you an opportunity to layout the content to match the new constraints. + *
    • + *
    • + * After every call to {@link #onLayout(PrintAttributes, PrintAttributes, + * CancellationSignal, LayoutResultCallback, Bundle)}, you may get a call to + * {@link #onWrite(PageRange[], ParcelFileDescriptor, CancellationSignal, WriteResultCallback)} + * asking you to write a PDF file with the content for specific pages. + *
    • + *
    • + * Finally, you will receive a call to {@link #onFinish()}. You can use this + * callback to release resources allocated in {@link #onStart()}. + *
    • + *
    + *

    + *

    Implementation

    + *

    + * The APIs defined in this class are designed to enable doing part or all + * of the work on an arbitrary thread. For example, if the printed content + * does not depend on the UI state, i.e. on what is shown on the screen, then + * you can offload the entire work on a dedicated thread, thus making your + * application interactive while the print work is being performed. + *

    + *

    + * You can also do work on different threads, for example if you print UI + * content, you can handle {@link #onStart()} and {@link #onLayout(PrintAttributes, + * 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[], 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 + * {@link #onLayout(PrintAttributes, PrintAttributes, CancellationSignal, + * LayoutResultCallback, Bundle)} which is not mandatory. If you use multiple + * threads, you are responsible for proper synchronization. + *

    + */ +public abstract class PrintDocumentAdapter { + + /** + * Extra: mapped to a boolean value that is true if + * the current layout is for a print preview, false otherwise. + */ + public static final String EXTRA_PRINT_PREVIEW = "EXTRA_PRINT_PREVIEW"; + + /** + * Called when printing starts. You can use this callback to allocate + * resources. This method is invoked on the main thread. + */ + public void onStart() { + /* do nothing - stub */ + } + + /** + * Called when the print attributes (page size, density, etc) changed + * giving you a chance to layout the content such that it matches the + * new constraints. This method is invoked on the main thread. + *

    + * After you are done laying out, you must invoke: {@link + * LayoutResultCallback#onLayoutFinished(PrintDocumentInfo, boolean)} with + * the last argument true or false depending on + * whether the layout changed the content or not, respectively; and {@link + * LayoutResultCallback#onLayoutFailed(CharSequence)}, if an error occurred. + * Note that you must call one of the methods of the given callback. + *

    + *

    + * Note: 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 + * CancellationSignal} upon invocation of which you should stop the + * layout. The cancellation callback will not be made on the main + * thread. + *

    + * + * @param oldAttributes The old print attributes. + * @param newAttributes The new print attributes. + * @param cancellationSignal Signal for observing cancel layout requests. + * @param callback Callback to inform the system for the layout result. + * @param extras Additional information about how to layout the content. + * + * @see LayoutResultCallback + * @see CancellationSignal + * @see #EXTRA_PRINT_PREVIEW + */ + public abstract void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + CancellationSignal cancellationSignal, LayoutResultCallback callback, + Bundle extras); + + /** + * Called when specific pages of the content should be written in the + * form of a PDF file to the given file descriptor. This method is invoked + * on the main thread. + *

    + * After you are done writing, you should close the file descriptor and + * invoke {@link WriteResultCallback #onWriteFinished(PageRange[]), if writing + * completed successfully; or {@link WriteResultCallback#onWriteFailed( + * CharSequence)}, if an error occurred. Note that you must call one of + * the methods of the given callback. + *

    + *

    + * Note: If the printed content is large, it is a good + * practice to schedule writing it on a dedicated thread and register an + * observer in the provided {@link CancellationSignal} upon invocation of + * which you should stop writing. The cancellation callback will not be + * made on the main thread. + *

    + * + * @param pages The pages whose content to print - non-overlapping in ascending order. + * @param destination The destination file descriptor to which to write. + * @param cancellationSignal Signal for observing cancel writing requests. + * @param callback Callback to inform the system for the write result. + * + * @see WriteResultCallback + * @see CancellationSignal + */ + public abstract void onWrite(PageRange[] pages, ParcelFileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback); + + /** + * Called when printing finishes. You can use this callback to release + * resources acquired in {@link #onStart()}. This method is invoked on + * the main thread. + */ + public void onFinish() { + /* do nothing - stub */ + } + + /** + * Base class for implementing a callback for the result of {@link + * PrintDocumentAdapter#onWrite(PageRange[], ParcelFileDescriptor, CancellationSignal, + * WriteResultCallback)}. + */ + public static abstract class WriteResultCallback { + + /** + * @hide + */ + public WriteResultCallback() { + /* do nothing - hide constructor */ + } + + /** + * Notifies that all the data was written. + * + * @param pages The pages that were written. Cannot be null or empty. + */ + public void onWriteFinished(PageRange[] pages) { + /* do nothing - stub */ + } + + /** + * Notifies that an error occurred while writing the data. + * + * @param error Error message. May be null if error is unknown. + */ + public void onWriteFailed(CharSequence error) { + /* do nothing - stub */ + } + + /** + * Notifies that write was cancelled as a result of a cancellation request. + */ + public void onWriteCancelled() { + /* do nothing - stub */ + } + } + + /** + * Base class for implementing a callback for the result of {@link + * PrintDocumentAdapter#onLayout(PrintAttributes, PrintAttributes, + * CancellationSignal, LayoutResultCallback, Bundle)}. + */ + public static abstract class LayoutResultCallback { + + /** + * @hide + */ + public LayoutResultCallback() { + /* do nothing - hide constructor */ + } + + /** + * Notifies that the layout finished and whether the content changed. + * + * @param info An info object describing the document. Cannot be null. + * @param changed Whether the layout changed. + * + * @see PrintDocumentInfo + */ + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + /* do nothing - stub */ + } + + /** + * Notifies that an error occurred while laying out the document. + * + * @param error Error message. May be null if error is unknown. + */ + public void onLayoutFailed(CharSequence error) { + /* do nothing - stub */ + } + + /** + * Notifies that layout was cancelled as a result of a cancellation request. + */ + public void onLayoutCancelled() { + /* do nothing - stub */ + } + } +} diff --git a/core/java/android/print/PrintDocumentInfo.aidl b/core/java/android/print/PrintDocumentInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..831dcb7ba1ae9afb51fa87afdc59059adda3e388 --- /dev/null +++ b/core/java/android/print/PrintDocumentInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PrintDocumentInfo; diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..b721ef4e0dfeff4994209afce71195fb5a4b363f --- /dev/null +++ b/core/java/android/print/PrintDocumentInfo.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * This class encapsulates information about a printed document. + */ +public final class PrintDocumentInfo implements Parcelable { + + /** + * Constant for unknown page count.. + */ + public static final int PAGE_COUNT_UNKNOWN = -1; + + /** + * Content type: unknown. + */ + public static final int CONTENT_TYPE_UNKNOWN = -1; + + /** + * Content type: document. + */ + public static final int CONTENT_TYPE_DOCUMENT = 0; + + /** + * Content type: photo. + */ + public static final int CONTENT_TYPE_PHOTO = 1; + + private String mName; + private int mPageCount; + private int mContentType; + private long mDataSize; + + /** + * Creates a new instance. + */ + private PrintDocumentInfo() { + /* do nothing */ + } + + /** + * Creates a new instance. + * + * @param Prototype from which to clone. + */ + private PrintDocumentInfo(PrintDocumentInfo prototype) { + mName = prototype.mName; + mPageCount = prototype.mPageCount; + mContentType = prototype.mContentType; + mDataSize = prototype.mDataSize; + } + + /** + * Creates a new instance. + * + * @param parcel Data from which to initialize. + */ + private PrintDocumentInfo(Parcel parcel) { + mName = parcel.readString(); + mPageCount = parcel.readInt(); + mContentType = parcel.readInt(); + mDataSize = parcel.readLong(); + } + + /** + * Gets the document name. + * + * @return The document name. + */ + public String getName() { + return mName; + } + + /** + * Gets the total number of pages. + * + * @return The number of pages. + * + * @see #PAGE_COUNT_UNKNOWN + */ + public int getPageCount() { + return mPageCount; + } + + /** + * Gets the content type. + * + * @return The content type. + * + * @see #CONTENT_TYPE_UNKNOWN + * @see #CONTENT_TYPE_DOCUMENT + * @see #CONTENT_TYPE_PHOTO + */ + public int getContentType() { + return mContentType; + } + + /** + * 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; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mName); + parcel.writeInt(mPageCount); + parcel.writeInt(mContentType); + parcel.writeLong(mDataSize); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mName != null) ? mName.hashCode() : 0); + result = prime * result + mContentType; + result = prime * result + mPageCount; + result = prime * result + (int) mDataSize; + result = prime * result + (int) mDataSize >> 32; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintDocumentInfo other = (PrintDocumentInfo) obj; + if (!TextUtils.equals(mName, other.mName)) { + return false; + } + if (mContentType != other.mContentType) { + return false; + } + if (mPageCount != other.mPageCount) { + return false; + } + if (mDataSize != other.mDataSize) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrintDocumentInfo{"); + builder.append("name=").append(mName); + builder.append(", pageCount=").append(mPageCount); + builder.append(", contentType=").append(contentTyepToString(mContentType)); + builder.append(", dataSize=").append(mDataSize); + builder.append("}"); + return builder.toString(); + } + + private String contentTyepToString(int contentType) { + switch (contentType) { + case CONTENT_TYPE_DOCUMENT: { + return "CONTENT_TYPE_DOCUMENT"; + } + case CONTENT_TYPE_PHOTO: { + return "CONTENT_TYPE_PHOTO"; + } + default: { + return "CONTENT_TYPE_UNKNOWN"; + } + } + } + + /** + * Builder for creating an {@link PrintDocumentInfo}. + */ + public static final class Builder { + private final PrintDocumentInfo mPrototype; + + /** + * Constructor. + *

    + * 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. + *

    + * + * @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; + } + + /** + * Sets the total number of pages. + *

    + * Default: {@link #PAGE_COUNT_UNKNOWN} + *

    + * + * @param pageCount The number of pages. Must be greater than + * or equal to zero or {@link PrintDocumentInfo#PAGE_COUNT_UNKNOWN}. + */ + public Builder setPageCount(int pageCount) { + if (pageCount < 0 && pageCount != PAGE_COUNT_UNKNOWN) { + throw new IllegalArgumentException("pageCount" + + " must be greater than or euqal to zero or" + + " DocumentInfo#PAGE_COUNT_UNKNOWN"); + } + mPrototype.mPageCount = pageCount; + return this; + } + + /** + * Sets the content type. + *

    + * Default: {@link #CONTENT_TYPE_UNKNOWN} + *

    + * + * @param type The content type. + * + * @see #CONTENT_TYPE_UNKNOWN + * @see #CONTENT_TYPE_DOCUMENT + * @see #CONTENT_TYPE_PHOTO + */ + public Builder setContentType(int type) { + mPrototype.mContentType = type; + return this; + } + + /** + * Creates a new {@link PrintDocumentInfo} instance. + * + * @return The new instance. + */ + public PrintDocumentInfo build() { + return new PrintDocumentInfo(mPrototype); + } + } + + public static final Parcelable.Creator CREATOR = + new Creator() { + @Override + public PrintDocumentInfo createFromParcel(Parcel parcel) { + return new PrintDocumentInfo(parcel); + } + + @Override + public PrintDocumentInfo[] newArray(int size) { + return new PrintDocumentInfo[size]; + } + }; +} diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..5d655bfe5bc28c7feef9115d9380c8931a19fda6 --- /dev/null +++ b/core/java/android/print/PrintFileDocumentAdapter.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.Context; +import android.os.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; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Adapter for printing PDF files. This class could be useful if you + * want to print a file and intercept when the system is ready + * spooling the data, so you can delete the file if it is a + * temporary one. To achieve this one must override {@link #onFinish()} + * and delete the file yourself. + * + * @hide + */ +public class PrintFileDocumentAdapter extends PrintDocumentAdapter { + + private static final String LOG_TAG = "PrintedFileDocumentAdapter"; + + private final Context mContext; + + private final File mFile; + + private final PrintDocumentInfo mDocumentInfo; + + private WriteFileAsyncTask mWriteFileAsyncTask; + + /** + * Constructor. + * + * @param context Context for accessing resources. + * @param file The PDF file to print. + * @param documentInfo The information about the printed file. + */ + public PrintFileDocumentAdapter(Context context, File file, + PrintDocumentInfo documentInfo) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null!"); + } + if (documentInfo == null) { + throw new IllegalArgumentException("documentInfo cannot be null!"); + } + mContext = context; + mFile = file; + mDocumentInfo = documentInfo; + } + + @Override + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + CancellationSignal cancellationSignal, LayoutResultCallback callback, + Bundle metadata) { + callback.onLayoutFinished(mDocumentInfo, false); + } + + @Override + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { + mWriteFileAsyncTask = new WriteFileAsyncTask(destination, cancellationSignal, callback); + mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, + (Void[]) null); + } + + private final class WriteFileAsyncTask extends AsyncTask { + + private final ParcelFileDescriptor mDestination; + + private final WriteResultCallback mResultCallback; + + private final CancellationSignal mCancellationSignal; + + public WriteFileAsyncTask(ParcelFileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { + mDestination = destination; + mResultCallback = callback; + mCancellationSignal = cancellationSignal; + mCancellationSignal.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel() { + cancel(true); + } + }); + } + + @Override + protected Void doInBackground(Void... params) { + InputStream in = null; + OutputStream out = new FileOutputStream(mDestination.getFileDescriptor()); + final byte[] buffer = new byte[8192]; + try { + in = new FileInputStream(mFile); + while (true) { + if (isCancelled()) { + break; + } + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); + } + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing data!", ioe); + mResultCallback.onWriteFailed(mContext.getString( + R.string.write_fail_reason_cannot_write)); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + mResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES}); + } + + @Override + protected void onCancelled(Void result) { + mResultCallback.onWriteFailed(mContext.getString( + R.string.write_fail_reason_cancelled)); + } + } +} + diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java new file mode 100644 index 0000000000000000000000000000000000000000..535ae43354fa07809e41b424962e11b1d5c3dac9 --- /dev/null +++ b/core/java/android/print/PrintJob.java @@ -0,0 +1,194 @@ +/* + * 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; + +/** + * This class represents a print job from the perspective of + * an application. + */ +public final class PrintJob { + + private final PrintManager mPrintManager; + + private PrintJobInfo mCachedInfo; + + PrintJob(PrintJobInfo info, PrintManager printManager) { + mCachedInfo = info; + mPrintManager = printManager; + } + + /** + * Gets the unique print job id. + * + * @return The id. + */ + public PrintJobId getId() { + return mCachedInfo.getId(); + } + + /** + * Gets the {@link PrintJobInfo} that describes this job. + *

    + * Node:The returned info object is a snapshot of the + * current print job state. Every call to this method returns a fresh + * info object that reflects the current print job state. + *

    + * + * @return The print job info. + */ + public PrintJobInfo getInfo() { + if (isInImmutableState()) { + return mCachedInfo; + } + PrintJobInfo info = mPrintManager.getPrintJobInfo(mCachedInfo.getId()); + if (info != null) { + mCachedInfo = info; + } + return mCachedInfo; + } + + /** + * Cancels this print job. You can request cancellation of a + * queued, started, blocked, or failed print job. + * + * @see #isQueued() + * @see #isStarted() + * @see #isBlocked() + * @see #isFailed() + */ + public void cancel() { + final int state = getInfo().getState(); + if (state == PrintJobInfo.STATE_QUEUED + || state == PrintJobInfo.STATE_STARTED + || state == PrintJobInfo.STATE_BLOCKED + || state == PrintJobInfo.STATE_FAILED) { + mPrintManager.cancelPrintJob(mCachedInfo.getId()); + } + } + + /** + * Restarts this print job. You can request restart of a failed + * print job. + * + * @see #isFailed() + */ + public void restart() { + if (isFailed()) { + mPrintManager.restartPrintJob(mCachedInfo.getId()); + } + } + + /** + * Gets whether this print job is queued. Such a print job is + * ready to be printed. You can request a cancellation via + * {@link #cancel()}. + * + * @return Whether the print job is queued. + * + * @see #cancel() + */ + public boolean isQueued() { + return getInfo().getState() == PrintJobInfo.STATE_QUEUED; + } + + /** + * Gets whether this print job is started. Such a print job is + * being printed. You can request a cancellation via + * {@link #cancel()}. + * + * @return Whether the print job is started. + * + * @see #cancel() + */ + public boolean isStarted() { + return getInfo().getState() == PrintJobInfo.STATE_STARTED; + } + + /** + * Gets whether this print job is blocked. Such a print job is halted + * due to an abnormal condition. You can request a cancellation via + * {@link #cancel()}. + * + * @return Whether the print job is blocked. + * + * @see #cancel() + */ + public boolean isBlocked() { + return getInfo().getState() == PrintJobInfo.STATE_BLOCKED; + } + + /** + * Gets whether this print job is completed. Such a print job + * is successfully printed. You can neither cancel nor restart + * such a print job. + * + * @return Whether the print job is completed. + */ + public boolean isCompleted() { + return getInfo().getState() == PrintJobInfo.STATE_COMPLETED; + } + + /** + * Gets whether this print job is failed. Such a print job is + * not successfully printed due to an error. You can request + * a restart via {@link #restart()}. + * + * @return Whether the print job is failed. + * + * @see #restart() + */ + public boolean isFailed() { + return getInfo().getState() == PrintJobInfo.STATE_FAILED; + } + + /** + * Gets whether this print job is cancelled. Such a print job was + * cancelled as a result of a user request. This is a final state. + * You cannot restart such a print job. + * + * @return Whether the print job is cancelled. + */ + public boolean isCancelled() { + return getInfo().getState() == PrintJobInfo.STATE_CANCELED; + } + + private boolean isInImmutableState() { + final int state = mCachedInfo.getState(); + return state == PrintJobInfo.STATE_COMPLETED + || state == PrintJobInfo.STATE_CANCELED; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintJob other = (PrintJob) obj; + return mCachedInfo.getId().equals(other.mCachedInfo.getId()); + } + + @Override + public int hashCode() { + return mCachedInfo.getId().hashCode(); + } +} diff --git a/core/java/android/print/PrintJobId.aidl b/core/java/android/print/PrintJobId.aidl new file mode 100644 index 0000000000000000000000000000000000000000..759f25f6d395158410f4f0fd8e419edca0fad396 --- /dev/null +++ b/core/java/android/print/PrintJobId.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PrintJobId; diff --git a/core/java/android/print/PrintJobId.java b/core/java/android/print/PrintJobId.java new file mode 100644 index 0000000000000000000000000000000000000000..01550e2a74136af2c38c059679109fe0e3494764 --- /dev/null +++ b/core/java/android/print/PrintJobId.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.UUID; + +/** + * This class represents the id of a print job. + */ +public final class PrintJobId implements Parcelable { + private final String mValue; + + /** + * Creates a new instance. + * + * @hide + */ + public PrintJobId() { + this(UUID.randomUUID().toString()); + } + + /** + * Creates a new instance. + * + * @param value The internal value. + * + * @hide + */ + public PrintJobId(String value) { + mValue = value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mValue != null) ? mValue.hashCode() : 0); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintJobId other = (PrintJobId) obj; + if (!TextUtils.equals(mValue, other.mValue)) { + return false; + } + return true; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mValue); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Flattens this id to a string. + * + * @return The flattened id. + * + * @hide + */ + public String flattenToString() { + return mValue; + } + + /** + * Unflattens a print job id from a string. + * + * @string The string. + * @return The unflattened id, or null if the string is malformed. + * + * @hide + */ + public static PrintJobId unflattenFromString(String string) { + return new PrintJobId(string); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public PrintJobId createFromParcel(Parcel parcel) { + return new PrintJobId(parcel.readString()); + } + + @Override + public PrintJobId[] newArray(int size) { + return new PrintJobId[size]; + } + }; +} diff --git a/core/java/android/print/PrintJobInfo.aidl b/core/java/android/print/PrintJobInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..fbca99c60ce1c2732192ad042b989365e30d8c25 --- /dev/null +++ b/core/java/android/print/PrintJobInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PrintJobInfo; diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..c6f0a6848f91a7514fcf97c5854db42530fc526c --- /dev/null +++ b/core/java/android/print/PrintJobInfo.java @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * This class represents the description of a print job. + */ +public final class PrintJobInfo implements Parcelable { + + /** + * Constant for matching any print job state. + * + * @hide + */ + public static final int STATE_ANY = -1; + + /** + * Constant for matching any print job state. + * + * @hide + */ + 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; + + /** + * Constant for matching any scheduled, i.e. delivered to a print + * service, print job state. + * + * @hide + */ + public static final int STATE_ANY_SCHEDULED = -4; + + /** + * Print job state: The print job is being created but not yet + * ready to be printed. + *

    + * Next valid states: {@link #STATE_QUEUED} + *

    + */ + public static final int STATE_CREATED = 1; + + /** + * Print job state: The print jobs is created, it is ready + * to be printed and should be processed. + *

    + * Next valid states: {@link #STATE_STARTED}, {@link #STATE_FAILED}, + * {@link #STATE_CANCELED} + *

    + */ + public static final int STATE_QUEUED = 2; + + /** + * Print job state: The print job is being printed. + *

    + * Next valid states: {@link #STATE_COMPLETED}, {@link #STATE_FAILED}, + * {@link #STATE_CANCELED}, {@link #STATE_BLOCKED} + *

    + */ + public static final int STATE_STARTED = 3; + + /** + * Print job state: The print job is blocked. + *

    + * Next valid states: {@link #STATE_FAILED}, {@link #STATE_CANCELED}, + * {@link #STATE_STARTED} + *

    + */ + public static final int STATE_BLOCKED = 4; + + /** + * Print job state: The print job was successfully printed. + * This is a terminal state. + *

    + * Next valid states: None + *

    + */ + public static final int STATE_COMPLETED = 5; + + /** + * Print job state: The print job was printing but printing failed. + * This is a terminal state. + *

    + * Next valid states: None + *

    + */ + public static final int STATE_FAILED = 6; + + /** + * Print job state: The print job was canceled. + * This is a terminal state. + *

    + * Next valid states: None + *

    + */ + public static final int STATE_CANCELED = 7; + + /** The unique print job id. */ + private PrintJobId mId; + + /** The human readable print job label. */ + private String mLabel; + + /** The unique id of the printer. */ + private PrinterId mPrinterId; + + /** The name of the printer - internally used */ + private String mPrinterName; + + /** The state of the print job. */ + private int mState; + + /** The id of the app that created the job. */ + private int mAppId; + + /** Optional tag assigned by a print service.*/ + private String mTag; + + /** The wall time when the print job was created. */ + private long mCreationTime; + + /** How many copies to print. */ + private int mCopies; + + /** Reason for the print job being in its current state. */ + private String mStateReason; + + /** The pages to print */ + private PageRange[] mPageRanges; + + /** The print job attributes size. */ + private PrintAttributes mAttributes; + + /** Information about the printed document. */ + private PrintDocumentInfo mDocumentInfo; + + /** Whether we are trying to cancel this print job. */ + private boolean mCanceling; + + /** @hide*/ + public PrintJobInfo() { + /* do nothing */ + } + + /** @hide */ + public PrintJobInfo(PrintJobInfo other) { + mId = other.mId; + mLabel = other.mLabel; + mPrinterId = other.mPrinterId; + mPrinterName = other.mPrinterName; + mState = other.mState; + mAppId = other.mAppId; + mTag = other.mTag; + mCreationTime = other.mCreationTime; + mCopies = other.mCopies; + mStateReason = other.mStateReason; + mPageRanges = other.mPageRanges; + mAttributes = other.mAttributes; + mDocumentInfo = other.mDocumentInfo; + mCanceling = other.mCanceling; + } + + private PrintJobInfo(Parcel parcel) { + mId = parcel.readParcelable(null); + mLabel = parcel.readString(); + mPrinterId = parcel.readParcelable(null); + mPrinterName = parcel.readString(); + mState = parcel.readInt(); + mAppId = parcel.readInt(); + mTag = parcel.readString(); + mCreationTime = parcel.readLong(); + mCopies = parcel.readInt(); + mStateReason = parcel.readString(); + if (parcel.readInt() == 1) { + Parcelable[] parcelables = parcel.readParcelableArray(null); + mPageRanges = new PageRange[parcelables.length]; + for (int i = 0; i < parcelables.length; i++) { + mPageRanges[i] = (PageRange) parcelables[i]; + } + } + if (parcel.readInt() == 1) { + mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel); + } + if (parcel.readInt() == 1) { + mDocumentInfo = PrintDocumentInfo.CREATOR.createFromParcel(parcel); + } + mCanceling = (parcel.readInt() == 1); + } + + /** + * Gets the unique print job id. + * + * @return The id. + */ + public PrintJobId getId() { + return mId; + } + + /** + * Sets the unique print job id. + * + * @param The job id. + * + * @hide + */ + public void setId(PrintJobId id) { + this.mId = id; + } + + /** + * Gets the human readable job label. + * + * @return The label. + */ + public String getLabel() { + return mLabel; + } + + /** + * Sets the human readable job label. + * + * @param label The label. + * + * @hide + */ + public void setLabel(String label) { + mLabel = label; + } + + /** + * Gets the unique target printer id. + * + * @return The target printer id. + */ + public PrinterId getPrinterId() { + return mPrinterId; + } + + /** + * Sets the unique target pritner id. + * + * @param printerId The target printer id. + * + * @hide + */ + public void setPrinterId(PrinterId printerId) { + mPrinterId = printerId; + } + + /** + * Gets the name of the target printer. + * + * @return The printer name. + * + * @hide + */ + public String getPrinterName() { + return mPrinterName; + } + + /** + * Sets the name of the target printer. + * + * @param printerName The printer name. + * + * @hide + */ + public void setPrinterName(String printerName) { + mPrinterName = printerName; + } + + /** + * Gets the current job state. + * + * @return The job state. + */ + public int getState() { + return mState; + } + + /** + * Sets the current job state. + * + * @param state The job state. + * + * @hide + */ + public void setState(int state) { + mState = state; + } + + /** + * Sets the owning application id. + * + * @return The owning app id. + * + * @hide + */ + public int getAppId() { + return mAppId; + } + + /** + * Sets the owning application id. + * + * @param appId The owning app id. + * + * @hide + */ + public void setAppId(int appId) { + mAppId = appId; + } + + /** + * Gets the optional tag assigned by a print service. + * + * @return The tag. + * + * @hide + */ + public String getTag() { + return mTag; + } + + /** + * Sets the optional tag assigned by a print service. + * + * @param tag The tag. + * + * @hide + */ + public void setTag(String tag) { + mTag = tag; + } + + /** + * Gets the wall time in millisecond when this print job was created. + * + * @return The creation time in milliseconds. + */ + public long getCreationTime() { + return mCreationTime; + } + + /** + * Sets the wall time in milliseconds when this print job was created. + * + * @param creationTime The creation time in milliseconds. + * + * @hide + */ + public void setCreationTime(long creationTime) { + if (creationTime < 0) { + throw new IllegalArgumentException("creationTime must be non-negative."); + } + mCreationTime = creationTime; + } + + /** + * Gets the number of copies. + * + * @return The number of copies or zero if not set. + */ + public int getCopies() { + return mCopies; + } + + /** + * Sets the number of copies. + * + * @param copyCount The number of copies. + * + * @hide + */ + public void setCopies(int copyCount) { + if (copyCount < 1) { + throw new IllegalArgumentException("Copies must be more than one."); + } + mCopies = copyCount; + } + + /** + * Gets the reason for the print job being in the current state. + * + * @return The reason, or null if there is no reason or the + * reason is unknown. + * + * @hide + */ + public String getStateReason() { + return mStateReason; + } + + /** + * Sets the reason for the print job being in the current state. + * + * @param stateReason The reason, or null if there is no reason + * or the reason is unknown. + * + * @hide + */ + public void setStateReason(String stateReason) { + mStateReason = stateReason; + } + + /** + * Gets the included pages. + * + * @return The included pages or null if not set. + */ + public PageRange[] getPages() { + return mPageRanges; + } + + /** + * Sets the included pages. + * + * @param pageRanges The included pages. + * + * @hide + */ + public void setPages(PageRange[] pageRanges) { + mPageRanges = pageRanges; + } + + /** + * Gets the print job attributes. + * + * @return The attributes. + */ + public PrintAttributes getAttributes() { + return mAttributes; + } + + /** + * Sets the print job attributes. + * + * @param attributes The attributes. + * + * @hide + */ + public void setAttributes(PrintAttributes attributes) { + mAttributes = attributes; + } + + /** + * Gets the info describing the printed document. + * + * @return The document info. + * + * @hide + */ + public PrintDocumentInfo getDocumentInfo() { + return mDocumentInfo; + } + + /** + * Sets the info describing the printed document. + * + * @param info The document info. + * + * @hide + */ + public void setDocumentInfo(PrintDocumentInfo info) { + mDocumentInfo = info; + } + + /** + * Gets whether this print is being cancelled. + * + * @return True if the print job is being cancelled. + * + * @hide + */ + public boolean isCancelling() { + return mCanceling; + } + + /** + * Sets whether this print is being cancelled. + * + * @param cancelling True if the print job is being cancelled. + * + * @hide + */ + public void setCancelling(boolean cancelling) { + mCanceling = cancelling; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mId, flags); + parcel.writeString(mLabel); + parcel.writeParcelable(mPrinterId, flags); + parcel.writeString(mPrinterName); + parcel.writeInt(mState); + parcel.writeInt(mAppId); + parcel.writeString(mTag); + parcel.writeLong(mCreationTime); + parcel.writeInt(mCopies); + parcel.writeString(mStateReason); + if (mPageRanges != null) { + parcel.writeInt(1); + parcel.writeParcelableArray(mPageRanges, flags); + } else { + parcel.writeInt(0); + } + if (mAttributes != null) { + parcel.writeInt(1); + mAttributes.writeToParcel(parcel, flags); + } else { + parcel.writeInt(0); + } + if (mDocumentInfo != null) { + parcel.writeInt(1); + mDocumentInfo.writeToParcel(parcel, flags); + } else { + parcel.writeInt(0); + } + parcel.writeInt(mCanceling ? 1 : 0); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrintJobInfo{"); + builder.append("label: ").append(mLabel); + builder.append(", id: ").append(mId); + builder.append(", state: ").append(stateToString(mState)); + builder.append(", printer: " + mPrinterId); + builder.append(", tag: ").append(mTag); + builder.append(", creationTime: " + mCreationTime); + builder.append(", copies: ").append(mCopies); + builder.append(", attributes: " + (mAttributes != null + ? mAttributes.toString() : null)); + builder.append(", documentInfo: " + (mDocumentInfo != null + ? mDocumentInfo.toString() : null)); + builder.append(", cancelling: " + mCanceling); + builder.append(", pages: " + (mPageRanges != null + ? Arrays.toString(mPageRanges) : null)); + builder.append("}"); + return builder.toString(); + } + + /** @hide */ + public static String stateToString(int state) { + switch (state) { + case STATE_CREATED: { + return "STATE_CREATED"; + } + case STATE_QUEUED: { + return "STATE_QUEUED"; + } + case STATE_STARTED: { + return "STATE_STARTED"; + } + case STATE_BLOCKED: { + return "STATE_BLOCKED"; + } + case STATE_FAILED: { + return "STATE_FAILED"; + } + case STATE_COMPLETED: { + return "STATE_COMPLETED"; + } + case STATE_CANCELED: { + return "STATE_CANCELED"; + } + default: { + return "STATE_UNKNOWN"; + } + } + } + + /** + * Builder for creating a {@link PrintJobInfo}. + */ + public static final class Builder { + private final PrintJobInfo mPrototype; + + /** + * Constructor. + * + * @param prototype Prototype to use as a starting point. + * Can be null. + */ + public Builder(PrintJobInfo prototype) { + mPrototype = (prototype != null) + ? new PrintJobInfo(prototype) + : new PrintJobInfo(); + } + + /** + * Sets the number of copies. + * + * @param copies The number of copies. + */ + public void setCopies(int copies) { + mPrototype.mCopies = copies; + } + + /** + * Sets the print job attributes. + * + * @param attributes The attributes. + */ + public void setAttributes(PrintAttributes attributes) { + mPrototype.mAttributes = attributes; + } + + /** + * Sets the included pages. + * + * @param pages The included pages. + */ + public void setPages(PageRange[] pages) { + mPrototype.mPageRanges = pages; + } + + /** + * Puts an advanced (printer specific) option. + * + * @param key The option key. + * @param value The option value. + */ + public void putAdvancedOption(String key, String value) { + + } + + /** + * Puts an advanced (printer specific) option. + * + * @param key The option key. + * @param value The option value. + */ + public void putAdvancedOption(String key, int value) { + + } + + /** + * Creates a new {@link PrintJobInfo} instance. + * + * @return The new instance. + */ + public PrintJobInfo build() { + return mPrototype; + } + } + + public static final Parcelable.Creator CREATOR = + new Creator() { + @Override + public PrintJobInfo createFromParcel(Parcel parcel) { + return new PrintJobInfo(parcel); + } + + @Override + public PrintJobInfo[] newArray(int size) { + return new PrintJobInfo[size]; + } + }; +} diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java new file mode 100644 index 0000000000000000000000000000000000000000..dbd8278d3a1fbee47410826d32fc8a6a30878a78 --- /dev/null +++ b/core/java/android/print/PrintManager.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.Context; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.PrintDocumentAdapter.LayoutResultCallback; +import android.print.PrintDocumentAdapter.WriteResultCallback; +import android.printservice.PrintServiceInfo; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.os.SomeArgs; + +import libcore.io.IoUtils; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * System level service for accessing the printing capabilities of the platform. + *

    + * To obtain a handle to the print manager do the following: + *

    + * + *
    + * PrintManager printManager =
    + *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
    + * 
    + */ +public final class PrintManager { + + private static final String LOG_TAG = "PrintManager"; + + private static final boolean DEBUG = false; + + private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1; + + /** + * The action for launching the print dialog activity. + * + * @hide + */ + public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG"; + + /** + * Extra with the intent for starting the print dialog. + *

    + * Type: {@link android.content.IntentSender} + *

    + * + * @hide + */ + public static final String EXTRA_PRINT_DIALOG_INTENT = + "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT"; + + /** + * Extra with a print job. + *

    + * Type: {@link android.print.PrintJobInfo} + *

    + * + * @hide + */ + public static final String EXTRA_PRINT_JOB = + "android.print.intent.extra.EXTRA_PRINT_JOB"; + + /** + * Extra with the print document adapter to be printed. + *

    + * Type: {@link android.print.IPrintDocumentAdapter} + *

    + * + * @hide + */ + public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = + "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER"; + + /** @hide */ + public static final int APP_ID_ANY = -2; + + private final Context mContext; + + private final IPrintManager mService; + + private final int mUserId; + + private final int mAppId; + + private final Handler mHandler; + + private Map mPrintJobStateChangeListeners; + + /** @hide */ + public interface PrintJobStateChangeListener { + + /** + * Callback notifying that a print job state changed. + * + * @param printJobId The print job id. + */ + public void onPrintJobStateChanged(PrintJobId printJobId); + } + + /** + * Creates a new instance. + * + * @param context The current context in which to operate. + * @param service The backing system service. + * @hide + */ + public PrintManager(Context context, IPrintManager service, int userId, int appId) { + mContext = context; + mService = service; + mUserId = userId; + mAppId = appId; + mHandler = new Handler(context.getMainLooper(), null, false) { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: { + SomeArgs args = (SomeArgs) message.obj; + PrintJobStateChangeListenerWrapper wrapper = + (PrintJobStateChangeListenerWrapper) args.arg1; + PrintJobStateChangeListener listener = wrapper.getListener(); + if (listener != null) { + PrintJobId printJobId = (PrintJobId) args.arg2; + listener.onPrintJobStateChanged(printJobId); + } + args.recycle(); + } break; + } + } + }; + } + + /** + * Creates an instance that can access all print jobs. + * + * @param userId The user id for which to get all print jobs. + * @return An instance if the caller has the permission to access all print + * jobs, null otherwise. + * @hide + */ + public PrintManager getGlobalPrintManagerForUser(int userId) { + return new PrintManager(mContext, mService, userId, APP_ID_ANY); + } + + PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { + try { + return mService.getPrintJobInfo(printJobId, mAppId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re); + } + return null; + } + + /** + * Adds a listener for observing the state of print jobs. + * + * @param listener The listener to add. + * @hide + */ + public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) { + if (mPrintJobStateChangeListeners == null) { + mPrintJobStateChangeListeners = new ArrayMap(); + } + PrintJobStateChangeListenerWrapper wrappedListener = + new PrintJobStateChangeListenerWrapper(listener, mHandler); + try { + mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId); + mPrintJobStateChangeListeners.put(listener, wrappedListener); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error adding print job state change listener", re); + } + } + + /** + * Removes a listener for observing the state of print jobs. + * + * @param listener The listener to remove. + * @hide + */ + public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) { + if (mPrintJobStateChangeListeners == null) { + return; + } + PrintJobStateChangeListenerWrapper wrappedListener = + mPrintJobStateChangeListeners.remove(listener); + if (wrappedListener == null) { + return; + } + if (mPrintJobStateChangeListeners.isEmpty()) { + mPrintJobStateChangeListeners = null; + } + wrappedListener.destroy(); + try { + mService.removePrintJobStateChangeListener(wrappedListener, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error removing print job state change listener", re); + } + } + + /** + * Gets a print job given its id. + * + * @return The print job list. + * @see PrintJob + * @hide + */ + public PrintJob getPrintJob(PrintJobId printJobId) { + try { + PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId); + if (printJob != null) { + return new PrintJob(printJob, this); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting print job", re); + } + return null; + } + + /** + * Gets the print jobs for this application. + * + * @return The print job list. + * @see PrintJob + */ + public List getPrintJobs() { + try { + List printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); + if (printJobInfos == null) { + return Collections.emptyList(); + } + final int printJobCount = printJobInfos.size(); + List printJobs = new ArrayList(printJobCount); + for (int i = 0; i < printJobCount; i++) { + printJobs.add(new PrintJob(printJobInfos.get(i), this)); + } + return printJobs; + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting print jobs", re); + } + return Collections.emptyList(); + } + + void cancelPrintJob(PrintJobId printJobId) { + try { + mService.cancelPrintJob(printJobId, mAppId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); + } + } + + void restartPrintJob(PrintJobId printJobId) { + try { + mService.restartPrintJob(printJobId, mAppId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re); + } + } + + /** + * Creates a print job for printing a {@link PrintDocumentAdapter} with + * default print attributes. + * + * @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 on success or null on failure. + * @see PrintJob + */ + public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter, + PrintAttributes attributes) { + if (TextUtils.isEmpty(printJobName)) { + throw new IllegalArgumentException("priintJobName cannot be empty"); + } + PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter, + mContext.getMainLooper()); + try { + Bundle result = mService.print(printJobName, delegate, + attributes, mContext.getPackageName(), mAppId, mUserId); + if (result != null) { + PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB); + IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT); + if (printJob == null || intent == null) { + return null; + } + try { + mContext.startIntentSender(intent, null, 0, 0, 0); + return new PrintJob(printJob, this); + } catch (SendIntentException sie) { + Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); + } + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error creating a print job", re); + } + return null; + } + + /** + * Gets the list of enabled print services. + * + * @return The enabled service list or an empty list. + * @hide + */ + public List getEnabledPrintServices() { + try { + List enabledServices = mService.getEnabledPrintServices(mUserId); + if (enabledServices != null) { + return enabledServices; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting the enabled print services", re); + } + return Collections.emptyList(); + } + + /** + * Gets the list of installed print services. + * + * @return The installed service list or an empty list. + * @hide + */ + public List getInstalledPrintServices() { + try { + List installedServices = mService.getInstalledPrintServices(mUserId); + if (installedServices != null) { + return installedServices; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting the installed print services", re); + } + return Collections.emptyList(); + } + + /** + * @hide + */ + public PrinterDiscoverySession createPrinterDiscoverySession() { + return new PrinterDiscoverySession(mService, mContext, mUserId); + } + + private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub { + + private final Object mLock = new Object(); + + private CancellationSignal mLayoutOrWriteCancellation; + + private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - + // cleared in finish() + + private Handler mHandler; // Strong reference OK - cleared in finish() + + private LayoutSpec mLastLayoutSpec; + + private WriteSpec mLastWriteSpec; + + private boolean mStartReqeusted; + private boolean mStarted; + + private boolean mFinishRequested; + private boolean mFinished; + + public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) { + mDocumentAdapter = documentAdapter; + mHandler = new MyHandler(looper); + } + + @Override + public void start() { + synchronized (mLock) { + // Started or finished - nothing to do. + if (mStartReqeusted || mFinishRequested) { + return; + } + + mStartReqeusted = true; + + doPendingWorkLocked(); + } + } + + @Override + public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + ILayoutResultCallback callback, Bundle metadata, int sequence) { + synchronized (mLock) { + // Start not called or finish called - nothing to do. + if (!mStartReqeusted || mFinishRequested) { + return; + } + + // Layout cancels write and overrides layout. + if (mLastWriteSpec != null) { + IoUtils.closeQuietly(mLastWriteSpec.fd); + mLastWriteSpec = null; + } + + mLastLayoutSpec = new LayoutSpec(); + mLastLayoutSpec.callback = callback; + mLastLayoutSpec.oldAttributes = oldAttributes; + mLastLayoutSpec.newAttributes = newAttributes; + mLastLayoutSpec.metadata = metadata; + mLastLayoutSpec.sequence = sequence; + + // Cancel the previous cancellable operation.When the + // cancellation completes we will do the pending work. + if (cancelPreviousCancellableOperationLocked()) { + return; + } + + doPendingWorkLocked(); + } + } + + @Override + public void write(PageRange[] pages, ParcelFileDescriptor fd, + IWriteResultCallback callback, int sequence) { + synchronized (mLock) { + // Start not called or finish called - nothing to do. + if (!mStartReqeusted || mFinishRequested) { + return; + } + + // Write cancels previous writes. + if (mLastWriteSpec != null) { + IoUtils.closeQuietly(mLastWriteSpec.fd); + mLastWriteSpec = null; + } + + mLastWriteSpec = new WriteSpec(); + mLastWriteSpec.callback = callback; + mLastWriteSpec.pages = pages; + mLastWriteSpec.fd = fd; + mLastWriteSpec.sequence = sequence; + + // Cancel the previous cancellable operation.When the + // cancellation completes we will do the pending work. + if (cancelPreviousCancellableOperationLocked()) { + return; + } + + doPendingWorkLocked(); + } + } + + @Override + public void finish() { + synchronized (mLock) { + // Start not called or finish called - nothing to do. + if (!mStartReqeusted || mFinishRequested) { + return; + } + + mFinishRequested = true; + + // When the current write or layout complete we + // will do the pending work. + if (mLastLayoutSpec != null || mLastWriteSpec != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Waiting for current operation"); + } + return; + } + + doPendingWorkLocked(); + } + } + + private boolean isFinished() { + return mDocumentAdapter == null; + } + + private void doFinish() { + mDocumentAdapter = null; + mHandler = null; + synchronized (mLock) { + mLayoutOrWriteCancellation = null; + } + } + + private boolean cancelPreviousCancellableOperationLocked() { + if (mLayoutOrWriteCancellation != null) { + mLayoutOrWriteCancellation.cancel(); + if (DEBUG) { + Log.i(LOG_TAG, "Cancelling previous operation"); + } + return true; + } + return false; + } + + private void doPendingWorkLocked() { + if (mStartReqeusted && !mStarted) { + mStarted = true; + mHandler.sendEmptyMessage(MyHandler.MSG_START); + } else if (mLastLayoutSpec != null) { + mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT); + } else if (mLastWriteSpec != null) { + mHandler.sendEmptyMessage(MyHandler.MSG_WRITE); + } else if (mFinishRequested && !mFinished) { + mFinished = true; + mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); + } + } + + private class LayoutSpec { + ILayoutResultCallback callback; + PrintAttributes oldAttributes; + PrintAttributes newAttributes; + Bundle metadata; + int sequence; + } + + private class WriteSpec { + IWriteResultCallback callback; + PageRange[] pages; + ParcelFileDescriptor fd; + int sequence; + } + + private final class MyHandler extends Handler { + public static final int MSG_START = 1; + public static final int MSG_LAYOUT = 2; + public static final int MSG_WRITE = 3; + public static final int MSG_FINISH = 4; + + public MyHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message message) { + if (isFinished()) { + return; + } + switch (message.what) { + case MSG_START: { + mDocumentAdapter.onStart(); + } + break; + + case MSG_LAYOUT: { + final CancellationSignal cancellation; + final LayoutSpec layoutSpec; + + synchronized (mLock) { + layoutSpec = mLastLayoutSpec; + mLastLayoutSpec = null; + cancellation = new CancellationSignal(); + mLayoutOrWriteCancellation = cancellation; + } + + if (layoutSpec != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Performing layout"); + } + mDocumentAdapter.onLayout(layoutSpec.oldAttributes, + layoutSpec.newAttributes, cancellation, + new MyLayoutResultCallback(layoutSpec.callback, + layoutSpec.sequence), layoutSpec.metadata); + } + } + break; + + case MSG_WRITE: { + final CancellationSignal cancellation; + final WriteSpec writeSpec; + + synchronized (mLock) { + writeSpec = mLastWriteSpec; + mLastWriteSpec = null; + cancellation = new CancellationSignal(); + mLayoutOrWriteCancellation = cancellation; + } + + if (writeSpec != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Performing write"); + } + mDocumentAdapter.onWrite(writeSpec.pages, writeSpec.fd, + cancellation, new MyWriteResultCallback(writeSpec.callback, + writeSpec.fd, writeSpec.sequence)); + } + } + break; + + case MSG_FINISH: { + if (DEBUG) { + Log.i(LOG_TAG, "Performing finish"); + } + mDocumentAdapter.onFinish(); + doFinish(); + } + break; + + default: { + throw new IllegalArgumentException("Unknown message: " + + message.what); + } + } + } + } + + private final class MyLayoutResultCallback extends LayoutResultCallback { + private ILayoutResultCallback mCallback; + private final int mSequence; + + public MyLayoutResultCallback(ILayoutResultCallback callback, + int sequence) { + mCallback = callback; + mSequence = sequence; + } + + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + if (info == null) { + throw new NullPointerException("document info cannot be null"); + } + final ILayoutResultCallback callback; + synchronized (mLock) { + callback = mCallback; + clearLocked(); + } + if (callback != null) { + try { + callback.onLayoutFinished(info, changed, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onLayoutFinished", re); + } + } + } + + @Override + public void onLayoutFailed(CharSequence error) { + final ILayoutResultCallback callback; + synchronized (mLock) { + callback = mCallback; + clearLocked(); + } + if (callback != null) { + try { + callback.onLayoutFailed(error, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onLayoutFailed", re); + } + } + } + + @Override + public void onLayoutCancelled() { + synchronized (mLock) { + clearLocked(); + } + } + + private void clearLocked() { + mLayoutOrWriteCancellation = null; + mCallback = null; + doPendingWorkLocked(); + } + } + + private final class MyWriteResultCallback extends WriteResultCallback { + private ParcelFileDescriptor mFd; + private int mSequence; + private IWriteResultCallback mCallback; + + public MyWriteResultCallback(IWriteResultCallback callback, + ParcelFileDescriptor fd, int sequence) { + mFd = fd; + mSequence = sequence; + mCallback = callback; + } + + @Override + public void onWriteFinished(PageRange[] pages) { + final IWriteResultCallback callback; + synchronized (mLock) { + callback = mCallback; + clearLocked(); + } + if (pages == null) { + throw new IllegalArgumentException("pages cannot be null"); + } + if (pages.length == 0) { + throw new IllegalArgumentException("pages cannot be empty"); + } + if (callback != null) { + try { + callback.onWriteFinished(pages, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteFinished", re); + } + } + } + + @Override + public void onWriteFailed(CharSequence error) { + final IWriteResultCallback callback; + synchronized (mLock) { + callback = mCallback; + clearLocked(); + } + if (callback != null) { + try { + callback.onWriteFailed(error, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteFailed", re); + } + } + } + + @Override + public void onWriteCancelled() { + synchronized (mLock) { + clearLocked(); + } + } + + private void clearLocked() { + mLayoutOrWriteCancellation = null; + IoUtils.closeQuietly(mFd); + mCallback = null; + mFd = null; + doPendingWorkLocked(); + } + } + } + + private static final class PrintJobStateChangeListenerWrapper extends + IPrintJobStateChangeListener.Stub { + private final WeakReference mWeakListener; + private final WeakReference mWeakHandler; + + public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, + Handler handler) { + mWeakListener = new WeakReference(listener); + mWeakHandler = new WeakReference(handler); + } + + @Override + public void onPrintJobStateChanged(PrintJobId printJobId) { + Handler handler = mWeakHandler.get(); + PrintJobStateChangeListener listener = mWeakListener.get(); + if (handler != null && listener != null) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = this; + args.arg2 = printJobId; + handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED, + args).sendToTarget(); + } + } + + public void destroy() { + mWeakListener.clear(); + } + + public PrintJobStateChangeListener getListener() { + return mWeakListener.get(); + } + } +} diff --git a/core/java/android/print/PrinterCapabilitiesInfo.aidl b/core/java/android/print/PrinterCapabilitiesInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0f5fb6b51978adb7b0c14f1b5a05d9b92705529b --- /dev/null +++ b/core/java/android/print/PrinterCapabilitiesInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PrinterCapabilitiesInfo; diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..df51ec10fb7900bfdd6cf776f30772584d1cb23b --- /dev/null +++ b/core/java/android/print/PrinterCapabilitiesInfo.java @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; +import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; +import android.print.PrintAttributes.Resolution; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class represents the capabilities of a printer. + */ +public final class PrinterCapabilitiesInfo implements Parcelable { + /** + * Undefined default value. + * + * @hide + */ + public static final int DEFAULT_UNDEFINED = -1; + + private static final int PROPERTY_MEDIA_SIZE = 0; + private static final int PROPERTY_RESOLUTION = 1; + private static final int PROPERTY_COLOR_MODE = 2; + private static final int PROPERTY_COUNT = 3; + + private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0); + + private Margins mMinMargins = DEFAULT_MARGINS; + private List mMediaSizes; + private List mResolutions; + + private int mColorModes; + + private final int[] mDefaults = new int[PROPERTY_COUNT]; + + /** + * @hide + */ + public PrinterCapabilitiesInfo() { + Arrays.fill(mDefaults, DEFAULT_UNDEFINED); + } + + /** + * @hide + */ + public PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype) { + copyFrom(prototype); + } + + /** + * @hide + */ + public void copyFrom(PrinterCapabilitiesInfo other) { + if (this == other) { + return; + } + + mMinMargins = other.mMinMargins; + + if (other.mMediaSizes != null) { + if (mMediaSizes != null) { + mMediaSizes.clear(); + mMediaSizes.addAll(other.mMediaSizes); + } else { + mMediaSizes = new ArrayList(other.mMediaSizes); + } + } else { + mMediaSizes = null; + } + + if (other.mResolutions != null) { + if (mResolutions != null) { + mResolutions.clear(); + mResolutions.addAll(other.mResolutions); + } else { + mResolutions = new ArrayList(other.mResolutions); + } + } else { + mResolutions = null; + } + + mColorModes = other.mColorModes; + + final int defaultCount = other.mDefaults.length; + for (int i = 0; i < defaultCount; i++) { + mDefaults[i] = other.mDefaults[i]; + } + } + + /** + * Gets the supported media sizes. + * + * @return The media sizes. + */ + public List getMediaSizes() { + return mMediaSizes; + } + + /** + * Gets the supported resolutions. + * + * @return The resolutions. + */ + public List getResolutions() { + return mResolutions; + } + + /** + * Gets the minimal margins. These are the minimal margins + * the printer physically supports. + * + * @return The minimal margins. + */ + public Margins getMinMargins() { + return mMinMargins; + } + + /** + * Gets the supported color modes. + * + * @return The color modes. + * + * @see PrintAttributes#COLOR_MODE_COLOR + * @see PrintAttributes#COLOR_MODE_MONOCHROME + */ + public int getColorModes() { + return mColorModes; + } + + /** + * Gets the default print attributes. + * + * @return The default attributes. + */ + public PrintAttributes getDefaults() { + PrintAttributes.Builder builder = new PrintAttributes.Builder(); + + builder.setMinMargins(mMinMargins); + + final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE]; + if (mediaSizeIndex >= 0) { + builder.setMediaSize(mMediaSizes.get(mediaSizeIndex)); + } + + final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION]; + if (resolutionIndex >= 0) { + builder.setResolution(mResolutions.get(resolutionIndex)); + } + + final int colorMode = mDefaults[PROPERTY_COLOR_MODE]; + if (colorMode > 0) { + builder.setColorMode(colorMode); + } + + return builder.build(); + } + + private PrinterCapabilitiesInfo(Parcel parcel) { + mMinMargins = readMargins(parcel); + readMediaSizes(parcel); + readResolutions(parcel); + + mColorModes = parcel.readInt(); + + readDefaults(parcel); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + writeMargins(mMinMargins, parcel); + writeMediaSizes(parcel); + writeResolutions(parcel); + + parcel.writeInt(mColorModes); + + writeDefaults(parcel); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode()); + result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode()); + result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode()); + result = prime * result + mColorModes; + result = prime * result + Arrays.hashCode(mDefaults); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrinterCapabilitiesInfo other = (PrinterCapabilitiesInfo) obj; + if (mMinMargins == null) { + if (other.mMinMargins != null) { + return false; + } + } else if (!mMinMargins.equals(other.mMinMargins)) { + return false; + } + if (mMediaSizes == null) { + if (other.mMediaSizes != null) { + return false; + } + } else if (!mMediaSizes.equals(other.mMediaSizes)) { + return false; + } + if (mResolutions == null) { + if (other.mResolutions != null) { + return false; + } + } else if (!mResolutions.equals(other.mResolutions)) { + return false; + } + if (mColorModes != other.mColorModes) { + return false; + } + if (!Arrays.equals(mDefaults, other.mDefaults)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrinterInfo{"); + builder.append("minMargins=").append(mMinMargins); + builder.append(", mediaSizes=").append(mMediaSizes); + builder.append(", resolutions=").append(mResolutions); + builder.append(", colorModes=").append(colorModesToString()); + builder.append("\"}"); + return builder.toString(); + } + + private String colorModesToString() { + StringBuilder builder = new StringBuilder(); + builder.append('['); + int colorModes = mColorModes; + while (colorModes != 0) { + final int colorMode = 1 << Integer.numberOfTrailingZeros(colorModes); + colorModes &= ~colorMode; + if (builder.length() > 1) { + builder.append(", "); + } + builder.append(PrintAttributes.colorModeToString(colorMode)); + } + builder.append(']'); + return builder.toString(); + } + + private void writeMediaSizes(Parcel parcel) { + if (mMediaSizes == null) { + parcel.writeInt(0); + return; + } + final int mediaSizeCount = mMediaSizes.size(); + parcel.writeInt(mediaSizeCount); + for (int i = 0; i < mediaSizeCount; i++) { + mMediaSizes.get(i).writeToParcel(parcel); + } + } + + private void readMediaSizes(Parcel parcel) { + final int mediaSizeCount = parcel.readInt(); + if (mediaSizeCount > 0 && mMediaSizes == null) { + mMediaSizes = new ArrayList(); + } + for (int i = 0; i < mediaSizeCount; i++) { + mMediaSizes.add(MediaSize.createFromParcel(parcel)); + } + } + + private void writeResolutions(Parcel parcel) { + if (mResolutions == null) { + parcel.writeInt(0); + return; + } + final int resolutionCount = mResolutions.size(); + parcel.writeInt(resolutionCount); + for (int i = 0; i < resolutionCount; i++) { + mResolutions.get(i).writeToParcel(parcel); + } + } + + private void readResolutions(Parcel parcel) { + final int resolutionCount = parcel.readInt(); + if (resolutionCount > 0 && mResolutions == null) { + mResolutions = new ArrayList(); + } + for (int i = 0; i < resolutionCount; i++) { + mResolutions.add(Resolution.createFromParcel(parcel)); + } + } + + private void writeMargins(Margins margins, Parcel parcel) { + if (margins == null) { + parcel.writeInt(0); + } else { + parcel.writeInt(1); + margins.writeToParcel(parcel); + } + } + + private Margins readMargins(Parcel parcel) { + return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; + } + + private void readDefaults(Parcel parcel) { + final int defaultCount = parcel.readInt(); + for (int i = 0; i < defaultCount; i++) { + mDefaults[i] = parcel.readInt(); + } + } + + private void writeDefaults(Parcel parcel) { + final int defaultCount = mDefaults.length; + parcel.writeInt(defaultCount); + for (int i = 0; i < defaultCount; i++) { + parcel.writeInt(mDefaults[i]); + } + } + + /** + * Builder for creating of a {@link PrinterInfo}. This class is responsible + * to enforce that all required attributes have at least one default value. + * In other words, this class creates only well-formed {@link PrinterInfo}s. + *

    + * Look at the individual methods for a reference whether a property is + * required or if it is optional. + *

    + */ + public static final class Builder { + private final PrinterCapabilitiesInfo mPrototype; + + /** + * Creates a new instance. + * + * @param printerId The printer id. Cannot be null. + * + * @throws IllegalArgumentException If the printer id is null. + */ + public Builder(PrinterId printerId) { + if (printerId == null) { + throw new IllegalArgumentException("printerId cannot be null."); + } + mPrototype = new PrinterCapabilitiesInfo(); + } + + /** + * Adds a supported media size. + *

    + * Required: Yes + *

    + * + * @param mediaSize A media size. + * @param isDefault Whether this is the default. + * @return This builder. + * @throws IllegalArgumentException If set as default and there + * is already a default. + * + * @see PrintAttributes.MediaSize + */ + public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) { + if (mPrototype.mMediaSizes == null) { + mPrototype.mMediaSizes = new ArrayList(); + } + final int insertionIndex = mPrototype.mMediaSizes.size(); + mPrototype.mMediaSizes.add(mediaSize); + if (isDefault) { + throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE); + mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex; + } + return this; + } + + /** + * Adds a supported resolution. + *

    + * Required: Yes + *

    + * + * @param resolution A resolution. + * @param isDefault Whether this is the default. + * @return This builder. + * + * @throws IllegalArgumentException If set as default and there + * is already a default. + * + * @see PrintAttributes.Resolution + */ + public Builder addResolution(Resolution resolution, boolean isDefault) { + if (mPrototype.mResolutions == null) { + mPrototype.mResolutions = new ArrayList(); + } + final int insertionIndex = mPrototype.mResolutions.size(); + mPrototype.mResolutions.add(resolution); + if (isDefault) { + throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION); + mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex; + } + return this; + } + + /** + * Sets the minimal margins. These are the minimal margins + * the printer physically supports. + * + *

    + * Required: Yes + *

    + * + * @param margins The margins. + * @return This builder. + * + * @throws IllegalArgumentException If margins are null. + * + * @see PrintAttributes.Margins + */ + public Builder setMinMargins(Margins margins) { + if (margins == null) { + throw new IllegalArgumentException("margins cannot be null"); + } + mPrototype.mMinMargins = margins; + return this; + } + + /** + * Sets the color modes. + *

    + * Required: Yes + *

    + * + * @param colorModes The color mode bit mask. + * @param defaultColorMode The default color mode. + * @return This builder. + * + * @throws IllegalArgumentException If color modes contains an invalid + * mode bit or if the default color mode is invalid. + * + * @see PrintAttributes#COLOR_MODE_COLOR + * @see PrintAttributes#COLOR_MODE_MONOCHROME + */ + public Builder setColorModes(int colorModes, int defaultColorMode) { + int currentModes = colorModes; + while (currentModes > 0) { + final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); + currentModes &= ~currentMode; + PrintAttributes.enforceValidColorMode(currentMode); + } + if ((colorModes & defaultColorMode) == 0) { + throw new IllegalArgumentException("Default color mode not in color modes."); + } + PrintAttributes.enforceValidColorMode(colorModes); + mPrototype.mColorModes = colorModes; + mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode; + return this; + } + + /** + * Crates a new {@link PrinterCapabilitiesInfo} enforcing that all + * required properties have need specified. See individual methods + * in this class for reference about required attributes. + * + * @return A new {@link PrinterCapabilitiesInfo}. + * + * @throws IllegalStateException If a required attribute was not specified. + */ + public PrinterCapabilitiesInfo build() { + if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) { + throw new IllegalStateException("No media size specified."); + } + if (mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] == DEFAULT_UNDEFINED) { + throw new IllegalStateException("No default media size specified."); + } + if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) { + throw new IllegalStateException("No resolution specified."); + } + if (mPrototype.mDefaults[PROPERTY_RESOLUTION] == DEFAULT_UNDEFINED) { + throw new IllegalStateException("No default resolution specified."); + } + if (mPrototype.mColorModes == 0) { + throw new IllegalStateException("No color mode specified."); + } + if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) { + throw new IllegalStateException("No default color mode specified."); + } + if (mPrototype.mMinMargins == null) { + throw new IllegalArgumentException("margins cannot be null"); + } + return new PrinterCapabilitiesInfo(mPrototype); + } + + private void throwIfDefaultAlreadySpecified(int propertyIndex) { + if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) { + throw new IllegalArgumentException("Default already specified."); + } + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public PrinterCapabilitiesInfo createFromParcel(Parcel parcel) { + return new PrinterCapabilitiesInfo(parcel); + } + + @Override + public PrinterCapabilitiesInfo[] newArray(int size) { + return new PrinterCapabilitiesInfo[size]; + } + }; +} + diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java new file mode 100644 index 0000000000000000000000000000000000000000..d32b71b9ed56b57e16c149cf0ed8f1db3d930f32 --- /dev/null +++ b/core/java/android/print/PrinterDiscoverySession.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * @hide + */ +public final class PrinterDiscoverySession { + + private static final String LOG_TAG ="PrinterDiscoverySession"; + + private static final int MSG_PRINTERS_ADDED = 1; + private static final int MSG_PRINTERS_REMOVED = 2; + + private final LinkedHashMap mPrinters = + new LinkedHashMap(); + + private final IPrintManager mPrintManager; + + private final int mUserId; + + private final Handler mHandler; + + private IPrinterDiscoveryObserver mObserver; + + private OnPrintersChangeListener mListener; + + private boolean mIsPrinterDiscoveryStarted; + + public static interface OnPrintersChangeListener { + public void onPrintersChanged(); + } + + PrinterDiscoverySession(IPrintManager printManager, Context context, int userId) { + mPrintManager = printManager; + mUserId = userId; + mHandler = new SessionHandler(context.getMainLooper()); + mObserver = new PrinterDiscoveryObserver(this); + try { + mPrintManager.createPrinterDiscoverySession(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error creating printer discovery session", re); + } + } + + public final void startPrinterDisovery(List priorityList) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed"); + return; + } + if (!mIsPrinterDiscoveryStarted) { + mIsPrinterDiscoveryStarted = true; + try { + mPrintManager.startPrinterDiscovery(mObserver, priorityList, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error starting printer discovery", re); + } + } + } + + public final void stopPrinterDiscovery() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring stop printers discovery - session destroyed"); + return; + } + if (mIsPrinterDiscoveryStarted) { + mIsPrinterDiscoveryStarted = false; + try { + mPrintManager.stopPrinterDiscovery(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error stopping printer discovery", re); + } + } + } + + 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 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 printerIds) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring validate printers - session destroyed"); + return; + } + try { + mPrintManager.validatePrinters(printerIds, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error validating printers", re); + } + } + + public final void destroy() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring destroy - session destroyed"); + } + destroyNoCheck(); + } + + public final List getPrinters() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring get printers - session destroyed"); + return Collections.emptyList(); + } + return new ArrayList(mPrinters.values()); + } + + public final boolean isDestroyed() { + throwIfNotCalledOnMainThread(); + return isDestroyedNoCheck(); + } + + public final boolean isPrinterDiscoveryStarted() { + throwIfNotCalledOnMainThread(); + return mIsPrinterDiscoveryStarted; + } + + public final void setOnPrintersChangeListener(OnPrintersChangeListener listener) { + throwIfNotCalledOnMainThread(); + mListener = listener; + } + + @Override + protected final void finalize() throws Throwable { + if (!isDestroyedNoCheck()) { + Log.e(LOG_TAG, "Destroying leaked printer discovery session"); + destroyNoCheck(); + } + super.finalize(); + } + + private boolean isDestroyedNoCheck() { + return (mObserver == null); + } + + private void destroyNoCheck() { + stopPrinterDiscovery(); + try { + mPrintManager.destroyPrinterDiscoverySession(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error destroying printer discovery session", re); + } finally { + mObserver = null; + mPrinters.clear(); + } + } + + private void handlePrintersAdded(List addedPrinters) { + if (isDestroyed()) { + return; + } + + // No old printers - do not bother keeping their position. + if (mPrinters.isEmpty()) { + final int printerCount = addedPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = addedPrinters.get(i); + mPrinters.put(printer.getId(), printer); + } + notifyOnPrintersChanged(); + return; + } + + // Add the printers to a map. + ArrayMap addedPrintersMap = + new ArrayMap(); + final int printerCount = addedPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = addedPrinters.get(i); + addedPrintersMap.put(printer.getId(), printer); + } + + // Update printers we already have. + for (PrinterId oldPrinterId : mPrinters.keySet()) { + PrinterInfo updatedPrinter = addedPrintersMap.remove(oldPrinterId); + if (updatedPrinter != null) { + mPrinters.put(oldPrinterId, updatedPrinter); + } + } + + // Add the new printers, i.e. what is left. + mPrinters.putAll(addedPrintersMap); + + // Announce the change. + notifyOnPrintersChanged(); + } + + private void handlePrintersRemoved(List printerIds) { + if (isDestroyed()) { + return; + } + boolean printersChanged = false; + final int removedPrinterIdCount = printerIds.size(); + for (int i = 0; i < removedPrinterIdCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + if (mPrinters.remove(removedPrinterId) != null) { + printersChanged = true; + } + } + if (printersChanged) { + notifyOnPrintersChanged(); + } + } + + private void notifyOnPrintersChanged() { + if (mListener != null) { + mListener.onPrintersChanged(); + } + } + + private static void throwIfNotCalledOnMainThread() { + if (!Looper.getMainLooper().isCurrentThread()) { + throw new IllegalAccessError("must be called from the main thread"); + } + } + + private final class SessionHandler extends Handler { + + public SessionHandler(Looper looper) { + super(looper, null, false); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MSG_PRINTERS_ADDED: { + List printers = (List) message.obj; + handlePrintersAdded(printers); + } break; + + case MSG_PRINTERS_REMOVED: { + List printerIds = (List) message.obj; + handlePrintersRemoved(printerIds); + } break; + } + } + } + + private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub { + + private final WeakReference mWeakSession; + + public PrinterDiscoveryObserver(PrinterDiscoverySession session) { + mWeakSession = new WeakReference(session); + } + + @Override + @SuppressWarnings("rawtypes") + public void onPrintersAdded(ParceledListSlice printers) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_ADDED, + printers.getList()).sendToTarget(); + } + } + + @Override + @SuppressWarnings("rawtypes") + public void onPrintersRemoved(ParceledListSlice printerIds) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_REMOVED, + printerIds.getList()).sendToTarget(); + } + } + } +} diff --git a/core/java/android/print/PrinterId.aidl b/core/java/android/print/PrinterId.aidl new file mode 100644 index 0000000000000000000000000000000000000000..84f422f1572206deb1193a810dc2b78c0ebaf687 --- /dev/null +++ b/core/java/android/print/PrinterId.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PrinterId; diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java new file mode 100644 index 0000000000000000000000000000000000000000..a3f3b2bfee11ba37f65eadfcdbd2ca7ea129923d --- /dev/null +++ b/core/java/android/print/PrinterId.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * This class represents the unique id of a printer. + */ +public final class PrinterId implements Parcelable { + + private final ComponentName mServiceName; + + private final String mLocalId; + + /** + * Creates a new instance. + * + * @param serviceName The managing print service. + * @param localId The locally unique id within the managing service. + * + * @hide + */ + public PrinterId(ComponentName serviceName, String localId) { + mServiceName = serviceName; + mLocalId = localId; + } + + private PrinterId(Parcel parcel) { + mServiceName = parcel.readParcelable(null); + mLocalId = parcel.readString(); + } + + /** + * The id of the print service this printer is managed by. + * + * @return The print service component name. + * + * @hide + */ + public ComponentName getServiceName() { + return mServiceName; + } + + /** + * Gets the id of this printer which is unique in the context + * of the print service that manages it. + * + * @return The printer name. + */ + public String getLocalId() { + return mLocalId; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mServiceName, flags); + parcel.writeString(mLocalId); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + PrinterId other = (PrinterId) object; + if (mServiceName == null) { + if (other.mServiceName != null) { + return false; + } + } else if (!mServiceName.equals(other.mServiceName)) { + return false; + } + if (!TextUtils.equals(mLocalId, other.mLocalId)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int hashCode = 1; + hashCode = prime * hashCode + ((mServiceName != null) + ? mServiceName.hashCode() : 1); + hashCode = prime * hashCode + mLocalId.hashCode(); + return hashCode; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrinterId{"); + builder.append("serviceName=").append(mServiceName.flattenToString()); + builder.append(", localId=").append(mLocalId); + builder.append('}'); + return builder.toString(); + } + + public static final Parcelable.Creator CREATOR = + new Creator() { + @Override + public PrinterId createFromParcel(Parcel parcel) { + return new PrinterId(parcel); + } + + @Override + public PrinterId[] newArray(int size) { + return new PrinterId[size]; + } + }; +} diff --git a/core/java/android/print/PrinterInfo.aidl b/core/java/android/print/PrinterInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..6ec5a58714e2744b802ad76d05866abb16167cfe --- /dev/null +++ b/core/java/android/print/PrinterInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable PrinterInfo; diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..ad79a38acb97a96595f1a0377f7c042a1c3a91d2 --- /dev/null +++ b/core/java/android/print/PrinterInfo.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * This class represents the description of a printer. + */ +public final class PrinterInfo implements Parcelable { + + /** Printer status: the printer is idle and ready to print. */ + public static final int STATUS_IDLE = 1; + + /** 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; + + private String mName; + + private int mStatus; + + private String mDescription; + + private PrinterCapabilitiesInfo mCapabilities; + + private PrinterInfo() { + /* do nothing */ + } + + private PrinterInfo(PrinterInfo prototype) { + copyFrom(prototype); + } + + /** + * @hide + */ + public void copyFrom(PrinterInfo other) { + if (this == other) { + return; + } + mId = other.mId; + mName = other.mName; + mStatus = other.mStatus; + mDescription = other.mDescription; + if (other.mCapabilities != null) { + if (mCapabilities != null) { + mCapabilities.copyFrom(other.mCapabilities); + } else { + mCapabilities = new PrinterCapabilitiesInfo(other.mCapabilities); + } + } else { + mCapabilities = null; + } + } + + /** + * Get the globally unique printer id. + * + * @return The printer id. + */ + public PrinterId getId() { + return mId; + } + + /** + * Get the printer name. + * + * @return The printer name. + */ + public String getName() { + return mName; + } + + /** + * Gets the printer status. + * + * @return The status. + */ + public int getStatus() { + return mStatus; + } + + /** + * Gets the printer description. + * + * @return The description. + */ + public String getDescription() { + return mDescription; + } + + /** + * Gets the printer capabilities. + * + * @return The capabilities. + */ + public PrinterCapabilitiesInfo getCapabilities() { + return mCapabilities; + } + + private PrinterInfo(Parcel parcel) { + mId = parcel.readParcelable(null); + mName = parcel.readString(); + mStatus = parcel.readInt(); + mDescription = parcel.readString(); + mCapabilities = parcel.readParcelable(null); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mId, flags); + parcel.writeString(mName); + parcel.writeInt(mStatus); + parcel.writeString(mDescription); + parcel.writeParcelable(mCapabilities, flags); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mId != null) ? mId.hashCode() : 0); + result = prime * result + ((mName != null) ? mName.hashCode() : 0); + result = prime * result + mStatus; + result = prime * result + ((mDescription != null) ? mDescription.hashCode() : 0); + result = prime * result + ((mCapabilities != null) ? mCapabilities.hashCode() : 0); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrinterInfo other = (PrinterInfo) obj; + if (mId == null) { + if (other.mId != null) { + return false; + } + } else if (!mId.equals(other.mId)) { + return false; + } + if (!TextUtils.equals(mName, other.mName)) { + return false; + } + if (mStatus != other.mStatus) { + return false; + } + if (!TextUtils.equals(mDescription, other.mDescription)) { + return false; + } + if (mCapabilities == null) { + if (other.mCapabilities != null) { + return false; + } + } else if (!mCapabilities.equals(other.mCapabilities)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrinterInfo{"); + builder.append("id=").append(mId); + builder.append(", name=").append(mName); + builder.append(", status=").append(mStatus); + builder.append(", description=").append(mDescription); + builder.append(", capabilities=").append(mCapabilities); + builder.append("\"}"); + return builder.toString(); + } + + /** + * Builder for creating of a {@link PrinterInfo}. + */ + public static final class Builder { + private final PrinterInfo mPrototype; + + /** + * Constructor. + * + * @param printerId The printer id. Cannot be null. + * @param name The printer name. Cannot be empty. + * @param status The printer status. Must be a valid status. + */ + public Builder(PrinterId printerId, String name, int status) { + if (printerId == null) { + throw new IllegalArgumentException("printerId cannot be null."); + } + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("name cannot be empty."); + } + if (!isValidStatus(status)) { + throw new IllegalArgumentException("status is invalid."); + } + mPrototype = new PrinterInfo(); + mPrototype.mId = printerId; + mPrototype.mName = name; + mPrototype.mStatus = status; + } + + /** + * Constructor. + * + * @param other Other info from which to start building. + */ + public Builder(PrinterInfo other) { + mPrototype = new PrinterInfo(); + mPrototype.copyFrom(other); + } + + /** + * 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. + * @return This builder. + */ + public Builder setName(String name) { + mPrototype.mName = name; + return this; + } + + /** + * Sets the printer description. + * + * @param description The description. + * @return This builder. + */ + public Builder setDescription(String description) { + mPrototype.mDescription = description; + return this; + } + + /** + * Sets the printer capabilities. + * + * @param capabilities The capabilities. + * @return This builder. + */ + public Builder setCapabilities(PrinterCapabilitiesInfo capabilities) { + mPrototype.mCapabilities = capabilities; + return this; + } + + /** + * Crates a new {@link PrinterInfo}. + * + * @return A new {@link PrinterInfo}. + */ + public PrinterInfo build() { + return new PrinterInfo(mPrototype); + } + + private boolean isValidStatus(int status) { + return (status == STATUS_IDLE + || status == STATUS_BUSY + || status == STATUS_UNAVAILABLE); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public PrinterInfo createFromParcel(Parcel parcel) { + return new PrinterInfo(parcel); + } + + @Override + public PrinterInfo[] newArray(int size) { + return new PrinterInfo[size]; + } + }; +} diff --git a/core/java/android/print/pdf/PrintedPdfDocument.java b/core/java/android/print/pdf/PrintedPdfDocument.java new file mode 100644 index 0000000000000000000000000000000000000000..2d8aafa1a573a78434dcb13a95677af234957922 --- /dev/null +++ b/core/java/android/print/pdf/PrintedPdfDocument.java @@ -0,0 +1,164 @@ +/* + * 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.graphics.pdf.PdfDocument; +import android.graphics.pdf.PdfDocument.Page; +import android.graphics.pdf.PdfDocument.PageInfo; +import android.print.PrintAttributes; +import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; + +/** + * This class is a helper for creating a PDF file for given print + * attributes. It is useful for implementing printing via the native + * Android graphics APIs. + *

    + * This class computes the page width, page height, and content rectangle + * from the provided print attributes and these precomputed values can be + * accessed via {@link #getPageWidth()}, {@link #getPageHeight()}, and + * {@link #getPageContentRect()}, respectively. The {@link #startPage(int)} + * methods creates pages whose {@link PageInfo} is initialized with the + * precomputed values for width, height, and content rectangle. + *

    + * A typical use of the APIs looks like this: + *

    + *
    + * // open a new document
    + * PrintedPdfDocument document = new PrintedPdfDocument(context,
    + *         printAttributes);
    + *
    + * // start a page
    + * Page page = document.startPage(0);
    + *
    + * // draw something on the page
    + * View content = getContentView();
    + * content.draw(page.getCanvas());
    + *
    + * // finish the page
    + * document.finishPage(page);
    + * . . .
    + * // add more pages
    + * . . .
    + * // write the document content
    + * document.writeTo(getOutputStream());
    + *
    + * //close the document
    + * document.close();
    + * 
    + */ +public class PrintedPdfDocument extends PdfDocument { + private static final int MILS_PER_INCH = 1000; + private static final int POINTS_IN_INCH = 72; + + private final int mPageWidth; + private final int mPageHeight; + private final Rect mContentRect; + + /** + * Creates a new document. + *

    + * Note: You must close the document after you are + * done by calling {@link #close()}. + *

    + * + * @param context Context instance for accessing resources. + * @param attributes The print attributes. + */ + public PrintedPdfDocument(Context context, PrintAttributes attributes) { + MediaSize mediaSize = attributes.getMediaSize(); + + // Compute the size of the target canvas from the attributes. + mPageWidth = (int) (((float) mediaSize.getWidthMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + mPageHeight = (int) (((float) mediaSize.getHeightMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + + // Compute the content size from the attributes. + Margins minMargins = attributes.getMinMargins(); + final int marginLeft = (int) (((float) minMargins.getLeftMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + final int marginTop = (int) (((float) minMargins.getTopMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + final int marginRight = (int) (((float) minMargins.getRightMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + final int marginBottom = (int) (((float) minMargins.getBottomMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + mContentRect = new Rect(marginLeft, marginTop, mPageWidth - marginRight, + mPageHeight - marginBottom); + } + + /** + * Starts a new page. The page is created using width, height and content + * rectangle computed from the print attributes passed in the constructor + * and the given page number to create an appropriate {@link PageInfo}. + *

    + * 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. + *

    + *

    + * Note: Do not call this method after {@link #close()}. + * Also do not call this method if the last page returned by this method + * is not finished by calling {@link #finishPage(Page)}. + *

    + * + * @param pageNumber The page number. Must be a positive value. + * @return A blank page. + * + * @see #finishPage(Page) + */ + public Page startPage(int pageNumber) { + PageInfo pageInfo = new PageInfo + .Builder(mPageWidth, mPageHeight, pageNumber) + .setContentRect(mContentRect) + .create(); + return startPage(pageInfo); + } + + /** + * Gets the page width. + * + * @return The page width in PostScript points (1/72th of an inch). + */ + public int getPageWidth() { + return mPageWidth; + } + + /** + * Gets the page height. + * + * @return The page height in PostScript points (1/72th of an inch). + */ + public int getPageHeight() { + return mPageHeight; + } + + /** + * Gets the content rectangle. This is the area of the page that + * contains printed data and is relative to the page top left. + * + * @return The content rectangle. + */ + public Rect getPageContentRect() { + return mContentRect; + } +} diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ee3661937395d8a195f13a00b0097eb1cab578b5 --- /dev/null +++ b/core/java/android/printservice/IPrintService.aidl @@ -0,0 +1,40 @@ +/* + * 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 languagÿe governing permissions and + * limitations under the License. + */ + +package android.printservice; + +import android.print.PrinterId; +import android.print.PrintJobInfo; +import android.printservice.IPrintServiceClient; + +/** + * Top-level interface to a print service component. + * + * @hide + */ +oneway interface IPrintService { + void setClient(IPrintServiceClient client); + void requestCancelPrintJob(in PrintJobInfo printJobInfo); + void onPrintJobQueued(in PrintJobInfo printJobInfo); + + void createPrinterDiscoverySession(); + void startPrinterDiscovery(in List priorityList); + void stopPrinterDiscovery(); + void validatePrinters(in List printerIds); + void startPrinterStateTracking(in PrinterId printerId); + void stopPrinterStateTracking(in PrinterId printerId); + void destroyPrinterDiscoverySession(); +} diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c2dfc30a4e25863f6e863f96eba73d759830d806 --- /dev/null +++ b/core/java/android/printservice/IPrintServiceClient.aidl @@ -0,0 +1,40 @@ +/* + * 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.printservice; + +import android.os.ParcelFileDescriptor; +import android.print.PrintJobInfo; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.print.PrintJobId; +import android.content.pm.ParceledListSlice; + +/** + * The top-level interface from a print service to the system. + * + * @hide + */ +interface IPrintServiceClient { + List getPrintJobInfos(); + PrintJobInfo getPrintJobInfo(in PrintJobId printJobId); + boolean setPrintJobState(in PrintJobId printJobId, int state, String error); + boolean setPrintJobTag(in PrintJobId printJobId, String tag); + oneway void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId); + + void onPrintersAdded(in ParceledListSlice printers); + void onPrintersRemoved(in ParceledListSlice printerIds); +} diff --git a/core/java/android/printservice/PrintDocument.java b/core/java/android/printservice/PrintDocument.java new file mode 100644 index 0000000000000000000000000000000000000000..e43f2a8363765ee426813d189a080c950d64628e --- /dev/null +++ b/core/java/android/printservice/PrintDocument.java @@ -0,0 +1,97 @@ +/* + * 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.printservice; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.PrintDocumentInfo; +import android.print.PrintJobId; +import android.util.Log; + +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. + *

    + * Note: All methods of this class must be executed on the + * main application thread. + *

    + */ +public final class PrintDocument { + + private static final String LOG_TAG = "PrintDocument"; + + private final PrintJobId mPrintJobId; + + private final IPrintServiceClient mPrintServiceClient; + + private final PrintDocumentInfo mInfo; + + PrintDocument(PrintJobId printJobId, IPrintServiceClient printServiceClient, + PrintDocumentInfo info) { + mPrintJobId = printJobId; + mPrintServiceClient = printServiceClient; + mInfo = info; + } + + /** + * Gets the {@link PrintDocumentInfo} that describes this document. + * + * @return The document info. + */ + public PrintDocumentInfo getInfo() { + PrintService.throwIfNotCalledOnMainThread(); + return mInfo; + } + + /** + * Gets the data associated with this document. + *

    + * Note: It is a responsibility of the client to open a + * stream to the returned file descriptor, fully read the data, and close + * the file descriptor. + *

    + * + * @return A file descriptor for reading the data. + */ + public ParcelFileDescriptor getData() { + PrintService.throwIfNotCalledOnMainThread(); + ParcelFileDescriptor source = null; + ParcelFileDescriptor sink = null; + try { + ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe(); + source = fds[0]; + sink = fds[1]; + mPrintServiceClient.writePrintJobData(sink, mPrintJobId); + return source; + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error calling getting print job data!", ioe); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling getting print job data!", re); + } finally { + if (sink != null) { + try { + sink.close(); + } catch (IOException ioe) { + /* ignore */ + } + } + } + return null; + } +} diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java new file mode 100644 index 0000000000000000000000000000000000000000..fdeb3730b90e1c244928fd18bf3ab5a2a4a0b07d --- /dev/null +++ b/core/java/android/printservice/PrintJob.java @@ -0,0 +1,392 @@ +/* + * 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.printservice; + +import android.os.RemoteException; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.text.TextUtils; +import android.util.Log; + +/** + * This class represents a print job from the perspective of a print + * service. It provides APIs for observing the print job state and + * performing operations on the print job. + *

    + * Note: All methods of this class must be invoked on + * the main application thread. + *

    + */ +public final class PrintJob { + + private static final String LOG_TAG = "PrintJob"; + + private final IPrintServiceClient mPrintServiceClient; + + private final PrintDocument mDocument; + + private PrintJobInfo mCachedInfo; + + PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client) { + mCachedInfo = jobInfo; + mPrintServiceClient = client; + mDocument = new PrintDocument(mCachedInfo.getId(), client, + jobInfo.getDocumentInfo()); + } + + /** + * Gets the unique print job id. + * + * @return The id. + */ + public PrintJobId getId() { + PrintService.throwIfNotCalledOnMainThread(); + return mCachedInfo.getId(); + } + + /** + * Gets the {@link PrintJobInfo} that describes this job. + *

    + * Node:The returned info object is a snapshot of the + * current print job state. Every call to this method returns a fresh + * info object that reflects the current print job state. + *

    + * + * @return The print job info. + */ + public PrintJobInfo getInfo() { + PrintService.throwIfNotCalledOnMainThread(); + if (isInImmutableState()) { + return mCachedInfo; + } + PrintJobInfo info = null; + try { + info = mPrintServiceClient.getPrintJobInfo(mCachedInfo.getId()); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Couldn't get info for job: " + mCachedInfo.getId(), re); + } + if (info != null) { + mCachedInfo = info; + } + return mCachedInfo; + } + + /** + * Gets the printed document. + * + * @return The document. + */ + public PrintDocument getDocument() { + PrintService.throwIfNotCalledOnMainThread(); + return mDocument; + } + + /** + * Gets whether this print job is queued. Such a print job is + * ready to be printed and can be started or cancelled. + * + * @return Whether the print job is queued. + * + * @see #start() + * @see #cancel() + */ + public boolean isQueued() { + PrintService.throwIfNotCalledOnMainThread(); + return getInfo().getState() == PrintJobInfo.STATE_QUEUED; + } + + /** + * Gets whether this print job is started. Such a print job is + * being printed and can be completed or canceled or failed. + * + * @return Whether the print job is started. + * + * @see #complete() + * @see #cancel() + * @see #fail(CharSequence) + */ + public boolean isStarted() { + PrintService.throwIfNotCalledOnMainThread(); + return getInfo().getState() == PrintJobInfo.STATE_STARTED; + } + + /** + * 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. + * + * @return Whether the print job is completed. + * + * @see #complete() + */ + public boolean isCompleted() { + PrintService.throwIfNotCalledOnMainThread(); + return getInfo().getState() == PrintJobInfo.STATE_COMPLETED; + } + + /** + * Gets whether this print job is failed. Such a print job is + * not successfully printed due to an error. This is a final state. + * + * @return Whether the print job is failed. + * + * @see #fail(CharSequence) + */ + public boolean isFailed() { + PrintService.throwIfNotCalledOnMainThread(); + return getInfo().getState() == PrintJobInfo.STATE_FAILED; + } + + /** + * Gets whether this print job is cancelled. Such a print job was + * cancelled as a result of a user request. This is a final state. + * + * @return Whether the print job is cancelled. + * + * @see #cancel() + */ + public boolean isCancelled() { + PrintService.throwIfNotCalledOnMainThread(); + return getInfo().getState() == PrintJobInfo.STATE_CANCELED; + } + + /** + * Starts the print job. You should call this method if {@link + * #isQueued()} or {@link #isBlocked()} returns true and you started + * resumed printing. + * + * @return Whether the job was started. + * + * @see #isQueued() + * @see #isBlocked() + */ + public boolean start() { + PrintService.throwIfNotCalledOnMainThread(); + 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. + * + * @return Whether the job as completed. + * + * @see #isStarted() + */ + public boolean complete() { + PrintService.throwIfNotCalledOnMainThread(); + if (isStarted()) { + return setState(PrintJobInfo.STATE_COMPLETED, null); + } + return false; + } + + /** + * Fails the print job. You should call this method if {@link + * #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. + * @return Whether the job was failed. + * + * @see #isQueued() + * @see #isStarted() + * @see #isBlocked() + */ + public boolean fail(String error) { + PrintService.throwIfNotCalledOnMainThread(); + if (!isInImmutableState()) { + return setState(PrintJobInfo.STATE_FAILED, error); + } + return false; + } + + /** + * Cancels the print job. You should call this method if {@link + * #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 (!isInImmutableState()) { + return setState(PrintJobInfo.STATE_CANCELED, null); + } + return false; + } + + /** + * Sets a tag that is valid in the context of a {@link PrintService} + * and is not interpreted by the system. For example, a print service + * may set as a tag the key of the print job returned by a remote + * print server, if the printing is off handed to a cloud based service. + * + * @param tag The tag. + * @return True if the tag was set, false otherwise. + */ + public boolean setTag(String tag) { + PrintService.throwIfNotCalledOnMainThread(); + if (isInImmutableState()) { + return false; + } + try { + return mPrintServiceClient.setPrintJobTag(mCachedInfo.getId(), tag); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting tag for job: " + mCachedInfo.getId(), re); + } + return false; + } + + /** + * Gets the print job tag. + * + * @return The tag or null. + * + * @see #setTag(String) + */ + public String getTag() { + PrintService.throwIfNotCalledOnMainThread(); + return getInfo().getTag(); + } + + /** + * Gets the value of an advanced (printer specific) print option. + * + * @param key The option key. + * @return The option value. + */ + public String getAdvancedStringOption(String key) { + PrintService.throwIfNotCalledOnMainThread(); + return null; + } + + /** + * Gets whether this job has a given advanced (printer specific) print + * option. + * + * @param key The option key. + * @return Whether the option is present. + */ + public boolean hasAdvancedOption(String key) { + PrintService.throwIfNotCalledOnMainThread(); + return false; + } + + /** + * Gets the value of an advanced (printer specific) print option. + * + * @param key The option key. + * @return The option value. + */ + public int getAdvancedIntOption(String key) { + PrintService.throwIfNotCalledOnMainThread(); + return 0; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintJob other = (PrintJob) obj; + return (mCachedInfo.getId().equals(other.mCachedInfo.getId())); + } + + @Override + public int hashCode() { + return mCachedInfo.getId().hashCode(); + } + + private boolean isInImmutableState() { + final int state = mCachedInfo.getState(); + return state == PrintJobInfo.STATE_COMPLETED + || state == PrintJobInfo.STATE_CANCELED + || state == PrintJobInfo.STATE_FAILED; + } + + private boolean setState(int state, String error) { + try { + if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) { + // Best effort - update the state of the cached info since + // 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.setStateReason(error); + return true; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting the state of job: " + mCachedInfo.getId(), re); + } + return false; + } +} diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java new file mode 100644 index 0000000000000000000000000000000000000000..0fc5f7f035a6a78658e6412af65d871964577a88 --- /dev/null +++ b/core/java/android/printservice/PrintService.java @@ -0,0 +1,530 @@ +/* + * 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.printservice; + +import android.R; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.print.PrintJobInfo; +import android.print.PrinterId; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + *

    + * This is the base class for implementing print services. A print service knows + * how to discover and interact one or more printers via one or more protocols. + *

    + *

    Printer discovery

    + *

    + * A print service is responsible for discovering printers, adding discovered printers, + * removing added printers, and updating added printers. When the system is interested + * in printers managed by your service it will call {@link + * #onCreatePrinterDiscoverySession()} from which you must return a new {@link + * PrinterDiscoverySession} instance. The returned session encapsulates the interaction + * between the system and your service during printer discovery. For description of this + * interaction refer to the documentation for {@link PrinterDiscoverySession}. + *

    + *

    + * For every printer discovery session all printers have to be added since system does + * not retain printers across sessions. Hence, each printer known to this print service + * should be added only once during a discovery session. Only an already added printer + * can be removed or updated. Removed printers can be added again. + *

    + *

    Print jobs

    + *

    + * When a new print job targeted to a printer managed by this print service is is queued, + * i.e. ready for processing by the print service, you will receive a call to {@link + * #onPrintJobQueued(PrintJob)}. The print service may handle the print job immediately + * or schedule that for an appropriate time in the future. The list of all active print + * jobs for this service is obtained by calling {@link #getActivePrintJobs()}. Active + * print jobs are ones that are queued or started. + *

    + *

    + * A print service is responsible for setting a print job's state as appropriate + * while processing it. Initially, a print job is queued, i.e. {@link PrintJob#isQueued() + * PrintJob.isQueued()} returns true, which means that the document to be printed is + * spooled by the system and the print service can begin processing it. You can obtain + * the printed document by calling {@link PrintJob#getDocument() PrintJob.getDocument()} + * whose data is accessed via {@link PrintDocument#getData() PrintDocument.getData()}. + * After the print service starts printing the data it should set the print job's + * state to started by calling {@link PrintJob#start()} after which + * {@link PrintJob#isStarted() PrintJob.isStarted()} would return true. Upon successful + * completion, the print job should be marked as completed by calling {@link + * PrintJob#complete() PrintJob.complete()} after which {@link PrintJob#isCompleted() + * PrintJob.isCompleted()} would return true. In case of a failure, the print job should + * be marked as failed by calling {@link PrintJob#fail(String) PrintJob.fail( + * String)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would + * return true. + *

    + *

    + * If a print job is queued or started and the user requests to cancel it, the print + * service will receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which + * requests from the service to do best effort in canceling the job. In case the job + * is successfully canceled, its state has to be marked as cancelled by calling {@link + * PrintJob#cancel() PrintJob.cancel()} after which {@link PrintJob#isCancelled() + * PrintJob.isCacnelled()} would return true. + *

    + *

    Lifecycle

    + *

    + * The lifecycle of a print service is managed exclusively by the system and follows + * the established service lifecycle. Additionally, starting or stopping a print service + * is triggered exclusively by an explicit user action through enabling or disabling it + * in the device settings. After the system binds to a print service, it calls {@link + * #onConnected()}. This method can be overriden by clients to perform post binding setup. + * Also after the system unbinds from a print service, it calls {@link #onDisconnected()}. + * This method can be overriden by clients to perform post unbinding cleanup. Your should + * not do any work after the system disconnected from your print service since the + * service can be killed at any time to reclaim memory. The system will not disconnect + * from a print service if there are active print jobs for the printers managed by it. + *

    + *

    Declaration

    + *

    + * A print service is declared as any other service in an AndroidManifest.xml but it must + * also specify that it handles the {@link android.content.Intent} with action {@link + * #SERVICE_INTERFACE android.printservice.PrintService}. Failure to declare this intent + * will cause the system to ignore the print service. Additionally, a print service must + * request the {@link android.Manifest.permission#BIND_PRINT_SERVICE + * android.permission.BIND_PRINT_SERVICE} permission to ensure that only the system can + * bind to it. Failure to declare this intent will cause the system to ignore the print + * service. Following is an example declaration: + *

    + *
    + * <service android:name=".MyPrintService"
    + *         android:permission="android.permission.BIND_PRINT_SERVICE">
    + *     <intent-filter>
    + *         <action android:name="android.printservice.PrintService" />
    + *     </intent-filter>
    + *     . . .
    + * </service>
    + * 
    + *

    Configuration

    + *

    + * A print service can be configured by specifying an optional settings activity which + * exposes service specific settings, an optional add printers activity which is used for + * manual addition of printers, vendor name ,etc. It is a responsibility of the system + * to launch the settings and add printers activities when appropriate. + *

    + *

    + * A print service is configured by providing a {@link #SERVICE_META_DATA meta-data} + * entry in the manifest when declaring the service. A service declaration with a meta-data + * tag is presented below: + *

     <service android:name=".MyPrintService"
    + *         android:permission="android.permission.BIND_PRINT_SERVICE">
    + *     <intent-filter>
    + *         <action android:name="android.printservice.PrintService" />
    + *     </intent-filter>
    + *     <meta-data android:name="android.printservice" android:resource="@xml/printservice" />
    + * </service>
    + *

    + *

    + * For more details for how to configure your print service via the meta-data refer to + * {@link #SERVICE_META_DATA} and <{@link android.R.styleable#PrintService + * print-service}>. + *

    + *

    + * Note: All callbacks in this class are executed on the main + * application thread. You should also invoke any method of this class on the main + * application thread. + *

    + */ +public abstract class PrintService extends Service { + + private static final String LOG_TAG = "PrintService"; + + private static final boolean DEBUG = false; + + /** + * The {@link Intent} action that must be declared as handled by a service + * in its manifest for the system to recognize it as a print service. + */ + public static final String SERVICE_INTERFACE = "android.printservice.PrintService"; + + /** + * Name under which a {@link PrintService} component publishes additional information + * about itself. This meta-data must reference a XML resource containing a + * <{@link android.R.styleable#PrintService print-service}> tag. This is + * a sample XML file configuring a print service: + *
     <print-service
    +     *     android:vendor="SomeVendor"
    +     *     android:settingsActivity="foo.bar.MySettingsActivity"
    +     *     andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
    +     *     . . .
    +     * />
    + *

    + * For detailed configuration options that can be specified via the meta-data + * refer to {@link android.R.styleable#PrintService android.R.styleable.PrintService}. + *

    + *

    + * If you declare a settings or add a printers activity, they have to be exported, + * by setting the {@link android.R.attr#exported} activity attribute to true + * . Also in case you want only the system to be able to start any of these + * activities you can specify that they request the android.permission + * .START_PRINT_SERVICE_CONFIG_ACTIVITY permission by setting the + * {@link android.R.attr#permission} activity attribute. + *

    + */ + public static final String SERVICE_META_DATA = "android.printservice"; + + /** + * If you declared an optional activity with advanced print options via the + * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity} + * attribute, this extra is used to pass in the currently constructed {@link + * PrintJobInfo} to your activity allowing you to modify it. After you are + * done, you must return the modified {@link PrintJobInfo} via the same extra. + *

    + * You cannot modify the passed in {@link PrintJobInfo} directly, rather you + * should build another one using the {@link PrintJobInfo.Builder} class. You + * can specify any standard properties and add advanced, printer specific, + * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String) + * PrintJobInfo.Builder#putAdvancedOption(String, String)} and {@link + * PrintJobInfo.Builder#putAdvancedOption(String, int) + * PrintJobInfo.Builder#putAdvancedOption(String, int)}. The advanced options + * are not interpreted by the system, they will not be visible to applications, + * and can only be accessed by your print service via {@link + * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)} + * and {@link PrintJob#getAdvancedIntOption(String) PrintJob.getAdvancedIntOption(String)}. + *

    + */ + public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO"; + + private Handler mHandler; + + private IPrintServiceClient mClient; + + private int mLastSessionId = -1; + + private PrinterDiscoverySession mDiscoverySession; + + @Override + protected final void attachBaseContext(Context base) { + super.attachBaseContext(base); + mHandler = new ServiceHandler(base.getMainLooper()); + } + + /** + * The system has connected to this service. + */ + protected void onConnected() { + /* do nothing */ + } + + /** + * The system has disconnected from this service. + */ + protected void onDisconnected() { + /* do nothing */ + } + + /** + * Callback asking you to create a new {@link PrinterDiscoverySession}. + * + * @see PrinterDiscoverySession + */ + protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession(); + + /** + * Called when cancellation of a print job is requested. The service + * should do best effort to fulfill the request. After the cancellation + * is performed, the print job should be marked as cancelled state by + * calling {@link PrintJob#cancel()}. + * + * @param printJob The print job to cancel. + * + * @see PrintJob#cancel() PrintJob.cancel() + * @see PrintJob#isCancelled() PrintJob.isCancelled() + */ + protected abstract void onRequestCancelPrintJob(PrintJob printJob); + + /** + * Called when there is a queued print job for one of the printers + * managed by this print service. + * + * @param printJob The new queued print job. + * + * @see PrintJob#isQueued() PrintJob.isQueued() + * @see #getActivePrintJobs() + */ + protected abstract void onPrintJobQueued(PrintJob printJob); + + /** + * Gets the active print jobs for the printers managed by this service. + * Active print jobs are ones that are not in a final state, i.e. whose + * state is queued or started. + * + * @return The active print jobs. + * + * @see PrintJob#isQueued() PrintJob.isQueued() + * @see PrintJob#isStarted() PrintJob.isStarted() + */ + public final List getActivePrintJobs() { + throwIfNotCalledOnMainThread(); + if (mClient == null) { + return Collections.emptyList(); + } + try { + List printJobs = null; + List printJobInfos = mClient.getPrintJobInfos(); + if (printJobInfos != null) { + final int printJobInfoCount = printJobInfos.size(); + printJobs = new ArrayList(printJobInfoCount); + for (int i = 0; i < printJobInfoCount; i++) { + printJobs.add(new PrintJob(printJobInfos.get(i), mClient)); + } + } + if (printJobs != null) { + return printJobs; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling getPrintJobs()", re); + } + return Collections.emptyList(); + } + + /** + * Generates a global printer id given the printer's locally unique one. + * + * @param localId A locally unique id in the context of your print service. + * @return Global printer id. + */ + public final PrinterId generatePrinterId(String localId) { + throwIfNotCalledOnMainThread(); + return new PrinterId(new ComponentName(getPackageName(), + getClass().getName()), localId); + } + + static void throwIfNotCalledOnMainThread() { + if (!Looper.getMainLooper().isCurrentThread()) { + throw new IllegalAccessError("must be called from the main thread"); + } + } + + @Override + public final IBinder onBind(Intent intent) { + return new IPrintService.Stub() { + @Override + public void createPrinterDiscoverySession() { + mHandler.sendEmptyMessage(ServiceHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION); + } + + @Override + public void destroyPrinterDiscoverySession() { + mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION); + } + + public void startPrinterDiscovery(List priorityList) { + mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY, + priorityList).sendToTarget(); + } + + @Override + public void stopPrinterDiscovery() { + mHandler.sendEmptyMessage(ServiceHandler.MSG_STOP_PRINTER_DISCOVERY); + } + + @Override + public void validatePrinters(List 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(); + } + + @Override + public void setClient(IPrintServiceClient client) { + mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client) + .sendToTarget(); + } + + @Override + public void requestCancelPrintJob(PrintJobInfo printJobInfo) { + mHandler.obtainMessage(ServiceHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB, + printJobInfo).sendToTarget(); + } + + @Override + public void onPrintJobQueued(PrintJobInfo printJobInfo) { + mHandler.obtainMessage(ServiceHandler.MSG_ON_PRINTJOB_QUEUED, + printJobInfo).sendToTarget(); + } + }; + } + + private final class ServiceHandler extends Handler { + public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1; + 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_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); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + final int action = message.what; + switch (action) { + case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_CREATE_PRINTER_DISCOVERY_SESSION " + + getPackageName()); + } + PrinterDiscoverySession session = onCreatePrinterDiscoverySession(); + if (session == null) { + throw new NullPointerException("session cannot be null"); + } + if (session.getId() == mLastSessionId) { + throw new IllegalStateException("cannot reuse session instances"); + } + mDiscoverySession = session; + mLastSessionId = session.getId(); + session.setObserver(mClient); + } break; + + case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_DESTROY_PRINTER_DISCOVERY_SESSION " + + getPackageName()); + } + if (mDiscoverySession != null) { + mDiscoverySession.destroy(); + mDiscoverySession = null; + } + } break; + + case MSG_START_PRINTER_DISCOVERY: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_START_PRINTER_DISCOVERY " + + getPackageName()); + } + if (mDiscoverySession != null) { + List priorityList = (ArrayList) message.obj; + mDiscoverySession.startPrinterDiscovery(priorityList); + } + } break; + + case MSG_STOP_PRINTER_DISCOVERY: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_STOP_PRINTER_DISCOVERY " + + getPackageName()); + } + if (mDiscoverySession != null) { + mDiscoverySession.stopPrinterDiscovery(); + } + } break; + + case MSG_VALIDATE_PRINTERS: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_VALIDATE_PRINTERS " + + getPackageName()); + } + if (mDiscoverySession != null) { + List printerIds = (List) message.obj; + mDiscoverySession.validatePrinters(printerIds); + } + } break; + + case MSG_START_PRINTER_STATE_TRACKING: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_START_PRINTER_STATE_TRACKING " + + getPackageName()); + } + if (mDiscoverySession != null) { + PrinterId printerId = (PrinterId) message.obj; + mDiscoverySession.startPrinterStateTracking(printerId); + } + } break; + + case MSG_STOP_PRINTER_STATE_TRACKING: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_STOP_PRINTER_STATE_TRACKING " + + getPackageName()); + } + if (mDiscoverySession != null) { + PrinterId printerId = (PrinterId) message.obj; + mDiscoverySession.stopPrinterStateTracking(printerId); + } + } break; + + case MSG_ON_REQUEST_CANCEL_PRINTJOB: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_ON_REQUEST_CANCEL_PRINTJOB " + + getPackageName()); + } + PrintJobInfo printJobInfo = (PrintJobInfo) message.obj; + onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient)); + } break; + + case MSG_ON_PRINTJOB_QUEUED: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_ON_PRINTJOB_QUEUED " + + getPackageName()); + } + PrintJobInfo printJobInfo = (PrintJobInfo) message.obj; + if (DEBUG) { + Log.i(LOG_TAG, "Queued: " + printJobInfo); + } + onPrintJobQueued(new PrintJob(printJobInfo, mClient)); + } break; + + case MSG_SET_CLEINT: { + if (DEBUG) { + Log.i(LOG_TAG, "MSG_SET_CLEINT " + + getPackageName()); + } + mClient = (IPrintServiceClient) message.obj; + if (mClient != null) { + onConnected(); + } else { + onDisconnected(); + } + } break; + + default: { + throw new IllegalArgumentException("Unknown message: " + action); + } + } + } + } +} diff --git a/core/java/android/printservice/PrintServiceInfo.aidl b/core/java/android/printservice/PrintServiceInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..95b67f2c18cdf11280f1d5093f7e40957f43125b --- /dev/null +++ b/core/java/android/printservice/PrintServiceInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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.printservice; + +parcelable PrintServiceInfo; diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..8e9636c2b7335016500f5e64b8cfba173e87a7bb --- /dev/null +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -0,0 +1,260 @@ +/* + * 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.printservice; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * This class describes a {@link PrintService}. A print service knows + * how to communicate with one or more printers over one or more protocols + * and exposes printers for use by the applications via the platform print + * APIs. + * + * @see PrintService + * @see android.print.PrintManager + * + * @hide + */ +public final class PrintServiceInfo implements Parcelable { + + private static final String LOG_TAG = PrintServiceInfo.class.getSimpleName(); + + private static final String TAG_PRINT_SERVICE = "print-service"; + + private final String mId; + + private final ResolveInfo mResolveInfo; + + private final String mSettingsActivityName; + + private final String mAddPrintersActivityName; + + /** + * Creates a new instance. + * + * @hide + */ + public PrintServiceInfo(Parcel parcel) { + mId = parcel.readString(); + mResolveInfo = parcel.readParcelable(null); + mSettingsActivityName = parcel.readString(); + mAddPrintersActivityName = parcel.readString(); + } + + /** + * Creates a new instance. + * + * @param resolveInfo The service resolve info. + * @param settingsActivityName Optional settings activity name. + * @param addPrintersActivityName Optional add printers activity name. + */ + public PrintServiceInfo(ResolveInfo resolveInfo, String settingsActivityName, + String addPrintersActivityName) { + mId = new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name).flattenToString(); + mResolveInfo = resolveInfo; + mSettingsActivityName = settingsActivityName; + mAddPrintersActivityName = addPrintersActivityName; + } + + /** + * Creates a new instance. + * + * @param resolveInfo The service resolve info. + * @param context Context for accessing resources. + * @throws XmlPullParserException If a XML parsing error occurs. + * @throws IOException If a I/O error occurs. + */ + public static PrintServiceInfo create(ResolveInfo resolveInfo, Context context) { + String settingsActivityName = null; + String addPrintersActivityName = null; + + XmlResourceParser parser = null; + PackageManager packageManager = context.getPackageManager(); + parser = resolveInfo.serviceInfo.loadXmlMetaData(packageManager, + PrintService.SERVICE_META_DATA); + if (parser != null) { + try { + int type = 0; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); + } + + String nodeName = parser.getName(); + if (!TAG_PRINT_SERVICE.equals(nodeName)) { + Log.e(LOG_TAG, "Ignoring meta-data that does not start with " + + TAG_PRINT_SERVICE + " tag"); + } else { + Resources resources = packageManager.getResourcesForApplication( + resolveInfo.serviceInfo.applicationInfo); + AttributeSet allAttributes = Xml.asAttributeSet(parser); + TypedArray attributes = resources.obtainAttributes(allAttributes, + com.android.internal.R.styleable.PrintService); + + settingsActivityName = attributes.getString( + com.android.internal.R.styleable.PrintService_settingsActivity); + + addPrintersActivityName = attributes.getString( + com.android.internal.R.styleable.PrintService_addPrintersActivity); + + attributes.recycle(); + } + } catch (IOException ioe) { + Log.w(LOG_TAG, "Error reading meta-data:" + ioe); + } catch (XmlPullParserException xppe) { + Log.w(LOG_TAG, "Error reading meta-data:" + xppe); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Unable to load resources for: " + + resolveInfo.serviceInfo.packageName); + } finally { + if (parser != null) { + parser.close(); + } + } + } + + return new PrintServiceInfo(resolveInfo, settingsActivityName, addPrintersActivityName); + } + + /** + * The accessibility service id. + *

    + * Generated by the system. + *

    + * + * @return The id. + */ + public String getId() { + return mId; + } + + /** + * The service {@link ResolveInfo}. + * + * @return The info. + */ + public ResolveInfo getResolveInfo() { + return mResolveInfo; + } + + /** + * The settings activity name. + *

    + * Statically set from + * {@link PrintService#SERVICE_META_DATA meta-data}. + *

    + * + * @return The settings activity name. + */ + public String getSettingsActivityName() { + return mSettingsActivityName; + } + + /** + * The add printers activity name. + *

    + * Statically set from + * {@link PrintService#SERVICE_META_DATA meta-data}. + *

    + * + * @return The add printers activity name. + */ + public String getAddPrintersActivityName() { + return mAddPrintersActivityName; + } + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flagz) { + parcel.writeString(mId); + parcel.writeParcelable(mResolveInfo, 0); + parcel.writeString(mSettingsActivityName); + parcel.writeString(mAddPrintersActivityName); + } + + @Override + public int hashCode() { + return 31 + ((mId == null) ? 0 : mId.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintServiceInfo other = (PrintServiceInfo) obj; + if (mId == null) { + if (other.mId != null) { + return false; + } + } else if (!mId.equals(other.mId)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrintServiceInfo{"); + builder.append("id=").append(mId); + builder.append(", resolveInfo=").append(mResolveInfo); + builder.append(", settingsActivityName=").append(mSettingsActivityName); + builder.append(", addPrintersActivityName=").append(mAddPrintersActivityName); + builder.append("}"); + return builder.toString(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public PrintServiceInfo createFromParcel(Parcel parcel) { + return new PrintServiceInfo(parcel); + } + + public PrintServiceInfo[] newArray(int size) { + return new PrintServiceInfo[size]; + } + }; +} diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java new file mode 100644 index 0000000000000000000000000000000000000000..17cb68f1cfcc870f8dac51d10ec651ed0de0bc66 --- /dev/null +++ b/core/java/android/printservice/PrinterDiscoverySession.java @@ -0,0 +1,528 @@ +/* + * 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.printservice; + +import android.content.pm.ParceledListSlice; +import android.os.RemoteException; +import android.print.PrinterCapabilitiesInfo; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.util.ArrayMap; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This class encapsulates the interaction between a print service and the + * system during printer discovery. During printer discovery you are responsible + * for adding discovered printers, removing previously added printers that + * disappeared, and updating already added printers. + *

    + * During the lifetime of this session you may be asked to start and stop + * performing printer discovery multiple times. You will receive a call to {@link + * PrinterDiscoverySession#onStartPrinterDiscovery(List)} to start printer + * discovery and a call to {@link PrinterDiscoverySession#onStopPrinterDiscovery()} + * to stop printer discovery. When the system is no longer interested in printers + * discovered by this session you will receive a call to {@link #onDestroy()} at + * which point the system will no longer call into the session and all the session + * methods will do nothing. + *

    + *

    + * Discovered printers are added by invoking {@link + * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared are + * removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. Added + * printers whose properties or capabilities changed are updated through a call to + * {@link PrinterDiscoverySession#addPrinters(List)}. The printers added in this + * session can be acquired via {@link #getPrinters()} where the returned printers + * will be an up-to-date snapshot of the printers that you reported during the + * session. Printers are not persisted across sessions. + *

    + *

    + * 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. When validating printers you do not need to + * provide the printers' capabilities but may do so. + *

    + *

    + * 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 must + * 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)}. + *

    + *

    + * Note: All callbacks in this class are executed on the main + * application thread. You also have to invoke any method of this class on the main + * application thread. + *

    + */ +public abstract class PrinterDiscoverySession { + private static final String LOG_TAG = "PrinterDiscoverySession"; + + private static int sIdCounter = 0; + + private final int mId; + + private final ArrayMap mPrinters = + new ArrayMap(); + + private final List mTrackedPrinters = + new ArrayList(); + + private ArrayMap mLastSentPrinters; + + private IPrintServiceClient mObserver; + + private boolean mIsDestroyed; + + private boolean mIsDiscoveryStarted; + + /** + * Constructor. + */ + public PrinterDiscoverySession() { + mId = sIdCounter++; + } + + void setObserver(IPrintServiceClient observer) { + mObserver = observer; + // If some printers were added in the method that + // created the session, send them over. + if (!mPrinters.isEmpty()) { + try { + mObserver.onPrintersAdded(new ParceledListSlice(getPrinters())); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending added printers", re); + } + } + } + + int getId() { + return mId; + } + + /** + * Gets the printers reported in this session. For example, if you add two + * printers and remove one of them, the returned list will contain only + * the printer that was added but not removed. + *

    + * Note: Calls to this method after the session is + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. + *

    + * + * @return The printers. + * + * @see #addPrinters(List) + * @see #removePrinters(List) + * @see #isDestroyed() + */ + public final List getPrinters() { + PrintService.throwIfNotCalledOnMainThread(); + if (mIsDestroyed) { + return Collections.emptyList(); + } + return new ArrayList(mPrinters.values()); + } + + /** + * Adds discovered printers. Adding an already added printer updates it. + * Removed printers can be added again. You can call this method multiple + * times during the life of this session. Duplicates will be ignored. + *

    + * Note: Calls to this method after the session is + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. + *

    + * + * @param printers The printers to add. + * + * @see #removePrinters(List) + * @see #getPrinters() + * @see #isDestroyed() + */ + public final void addPrinters(List printers) { + PrintService.throwIfNotCalledOnMainThread(); + + // If the session is destroyed - nothing do to. + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not adding printers - session destroyed."); + return; + } + + if (mIsDiscoveryStarted) { + // If during discovery, add the new printers and send them. + List addedPrinters = null; + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo addedPrinter = printers.get(i); + PrinterInfo oldPrinter = mPrinters.put(addedPrinter.getId(), addedPrinter); + if (oldPrinter == null || !oldPrinter.equals(addedPrinter)) { + if (addedPrinters == null) { + addedPrinters = new ArrayList(); + } + addedPrinters.add(addedPrinter); + } + } + + // Send the added printers, if such. + if (addedPrinters != null) { + try { + mObserver.onPrintersAdded(new ParceledListSlice(addedPrinters)); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending added printers", re); + } + } + } else { + // Remember the last sent printers if needed. + if (mLastSentPrinters == null) { + mLastSentPrinters = new ArrayMap(mPrinters); + } + + // Update the printers. + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo addedPrinter = printers.get(i); + if (mPrinters.get(addedPrinter.getId()) == null) { + mPrinters.put(addedPrinter.getId(), addedPrinter); + } + } + } + } + + /** + * Removes added printers. Removing an already removed or never added + * printer has no effect. Removed printers can be added again. You can + * call this method multiple times during the lifetime of this session. + *

    + * Note: Calls to this method after the session is + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. + *

    + * + * @param printerIds The ids of the removed printers. + * + * @see #addPrinters(List) + * @see #getPrinters() + * @see #isDestroyed() + */ + public final void removePrinters(List printerIds) { + PrintService.throwIfNotCalledOnMainThread(); + + // If the session is destroyed - nothing do to. + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not removing printers - session destroyed."); + return; + } + + if (mIsDiscoveryStarted) { + // If during discovery, remove existing printers and send them. + List removedPrinterIds = new ArrayList(); + final int removedPrinterIdCount = printerIds.size(); + for (int i = 0; i < removedPrinterIdCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + if (mPrinters.remove(removedPrinterId) != null) { + removedPrinterIds.add(removedPrinterId); + } + } + + // Send the removed printers, if such. + if (!removedPrinterIds.isEmpty()) { + try { + mObserver.onPrintersRemoved(new ParceledListSlice( + removedPrinterIds)); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending removed printers", re); + } + } + } else { + // Remember the last sent printers if needed. + if (mLastSentPrinters == null) { + mLastSentPrinters = new ArrayMap(mPrinters); + } + + // Update the printers. + final int removedPrinterIdCount = printerIds.size(); + for (int i = 0; i < removedPrinterIdCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + mPrinters.remove(removedPrinterId); + } + } + } + + private void sendOutOfDiscoveryPeriodPrinterChanges() { + // Noting changed since the last discovery period - nothing to do. + if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) { + mLastSentPrinters = null; + return; + } + + // Determine the added printers. + List addedPrinters = null; + for (PrinterInfo printer : mPrinters.values()) { + PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId()); + if (sentPrinter == null || !sentPrinter.equals(printer)) { + if (addedPrinters == null) { + addedPrinters = new ArrayList(); + } + addedPrinters.add(printer); + } + } + + // Send the added printers, if such. + if (addedPrinters != null) { + try { + mObserver.onPrintersAdded(new ParceledListSlice(addedPrinters)); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending added printers", re); + } + } + + // Determine the removed printers. + List removedPrinterIds = null; + for (PrinterInfo sentPrinter : mLastSentPrinters.values()) { + if (!mPrinters.containsKey(sentPrinter.getId())) { + if (removedPrinterIds == null) { + removedPrinterIds = new ArrayList(); + } + removedPrinterIds.add(sentPrinter.getId()); + } + } + + // Send the removed printers, if such. + if (removedPrinterIds != null) { + try { + mObserver.onPrintersRemoved(new ParceledListSlice(removedPrinterIds)); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending removed printers", re); + } + } + + mLastSentPrinters = null; + } + + /** + * Callback asking you to start printer discovery. Discovered printers should be + * added via calling {@link #addPrinters(List)}. Added printers that disappeared + * should be removed via calling {@link #removePrinters(List)}. Added printers + * whose properties or capabilities changed should be updated via calling {@link + * #addPrinters(List)}. You will receive a call to {@link #onStopPrinterDiscovery()} + * when you should stop printer discovery. + *

    + * During the lifetime of this session all printers that are known to your print + * service have to be added. The system does not retain any printers across sessions. + * However, if you were asked to start and then stop performing printer discovery + * in this session, then a subsequent discovering should not re-discover already + * discovered printers. You can get the printers reported during this session by + * calling {@link #getPrinters()}. + *

    + *

    + * Note: 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. You do not need + * to provide the capabilities of the printers, rather verify whether they exist + * similarly to {@link #onValidatePrinters(List)}. + *

    + * + * @param priorityList The list of printers to validate first. Never null. + * + * @see #onStopPrinterDiscovery() + * @see #addPrinters(List) + * @see #removePrinters(List) + * @see #isPrinterDiscoveryStarted() + */ + public abstract void onStartPrinterDiscovery(List priorityList); + + /** + * Callback notifying you that you should stop printer discovery. + * + * @see #onStartPrinterDiscovery(List) + * @see #isPrinterDiscoveryStarted() + */ + public abstract void onStopPrinterDiscovery(); + + /** + * 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 #addPrinters(List)}. + *

    + * Note: You are not required to provide + * the printer capabilities when updating the printers that do exist. + *

    + * + * @param printerIds The printers to validate. + * + * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) + * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) + */ + public abstract void onValidatePrinters(List 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 #addPrinters(List)}. + *

    + * Note: A printer can be initially added without its + * capabilities to avoid polling printers that the user will not select. + * However, after this method is called you are expected to update the + * printer including its capabilities. Otherwise, the + * printer will be ignored. + *

    + *

    + * 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. + *

    + * + * @param printerId The printer to start tracking. + * + * @see #onStopPrinterStateTracking(PrinterId) + * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) + * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) + */ + 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); + + /** + * Gets the printers that should be tracked. These are printers that are + * important to the user and for which you received a call to {@link + * #onStartPrinterStateTracking(PrinterId)} asking you to observer their + * state and reporting it to the system via {@link #addPrinters(List)}. + * You will receive a call to {@link #onStopPrinterStateTracking(PrinterId)} + * if you should stop tracking a printer. + *

    + * Note: Calls to this method after the session is + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. + *

    + * + * @return The printers. + * + * @see #onStartPrinterStateTracking(PrinterId) + * @see #onStopPrinterStateTracking(PrinterId) + * @see #isDestroyed() + */ + public final List getTrackedPrinters() { + PrintService.throwIfNotCalledOnMainThread(); + if (mIsDestroyed) { + return Collections.emptyList(); + } + return new ArrayList(mTrackedPrinters); + } + + /** + * Notifies you that the session is destroyed. After this callback is invoked + * any calls to the methods of this class will be ignored, {@link #isDestroyed()} + * will return true and you will also no longer receive callbacks. + * + * @see #isDestroyed() + */ + public abstract void onDestroy(); + + /** + * Gets whether the session is destroyed. + * + * @return Whether the session is destroyed. + * + * @see #onDestroy() + */ + public final boolean isDestroyed() { + PrintService.throwIfNotCalledOnMainThread(); + return mIsDestroyed; + } + + /** + * Gets whether printer discovery is started. + * + * @return Whether printer discovery is destroyed. + * + * @see #onStartPrinterDiscovery(List) + * @see #onStopPrinterDiscovery() + */ + public final boolean isPrinterDiscoveryStarted() { + PrintService.throwIfNotCalledOnMainThread(); + return mIsDiscoveryStarted; + } + + void startPrinterDiscovery(List priorityList) { + if (!mIsDestroyed) { + mIsDiscoveryStarted = true; + sendOutOfDiscoveryPeriodPrinterChanges(); + if (priorityList == null) { + priorityList = Collections.emptyList(); + } + onStartPrinterDiscovery(priorityList); + } + } + + void stopPrinterDiscovery() { + if (!mIsDestroyed) { + mIsDiscoveryStarted = false; + onStopPrinterDiscovery(); + } + } + + void validatePrinters(List printerIds) { + if (!mIsDestroyed && mObserver != null) { + onValidatePrinters(printerIds); + } + } + + void startPrinterStateTracking(PrinterId printerId) { + if (!mIsDestroyed && mObserver != null + && !mTrackedPrinters.contains(printerId)) { + mTrackedPrinters.add(printerId); + onStartPrinterStateTracking(printerId); + } + } + + void stopPrinterStateTracking(PrinterId printerId) { + if (!mIsDestroyed && mObserver != null + && mTrackedPrinters.remove(printerId)) { + onStopPrinterStateTracking(printerId); + } + } + + void destroy() { + if (!mIsDestroyed) { + mIsDestroyed = true; + mIsDiscoveryStarted = false; + mPrinters.clear(); + mLastSentPrinters = null; + mObserver = null; + onDestroy(); + } + } +} diff --git a/core/java/android/printservice/package.html b/core/java/android/printservice/package.html new file mode 100644 index 0000000000000000000000000000000000000000..2fb06bd00b3943cbb687aa17512ef58b3a01e2c1 --- /dev/null +++ b/core/java/android/printservice/package.html @@ -0,0 +1,23 @@ + + +

    +Provides classes for implementing print services. Print services are plug-in components +that know how to talk to printers via some standard protocols. These services serve as a +bridge between the system and the printers. Hence, the printer and print protocol specific +implementation is factored out of the system and can be independently developed and updated. +

    +

    +A print service implementation should extend {@link android.printservice.PrintService} +and implement its abstract methods. Also the print service has to follow the contract for +managing {@link android.printservice.PrintJob}s. +

    +

    +The system is responsible for starting and stopping a print service depending on whether +there are active print jobs for the printers managed by the service. The print service +should also perform printer discovery in a timely fashion to ensure good user experience. +The interaction between the system and the print service during printer discovery is +encapsulated by a {@link android.printservice.PrinterDiscoverySession} instance created +by the print service when requested by the system. +

    + + diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java index 3401cb1018f8c69cfe2dbfebae0e3761d59abdc2..724d76d8bcbd0a3ff6b40f32074fdd3f7bca16ed 100644 --- a/core/java/android/provider/AlarmClock.java +++ b/core/java/android/provider/AlarmClock.java @@ -21,12 +21,12 @@ import android.annotation.SdkConstant.SdkConstantType; /** * The AlarmClock provider contains an Intent action and extras that can be used - * to start an Activity to set a new alarm in an alarm clock application. + * to start an Activity to set a new alarm or timer in an alarm clock application. * - * Applications that wish to receive the ACTION_SET_ALARM Intent should create - * an activity to handle the Intent that requires the permission + * Applications that wish to receive the ACTION_SET_ALARM and ACTION_SET_TIMER Intents + * should create an activity to handle the Intent that requires the permission * com.android.alarm.permission.SET_ALARM. Applications that wish to create a - * new alarm should use + * new alarm or timer should use * {@link android.content.Context#startActivity Context.startActivity()} so that * the user has the option of choosing which alarm clock application to use. */ @@ -34,49 +34,203 @@ public final class AlarmClock { /** * Activity Action: Set an alarm. *

    - * Input: Nothing. - *

    - * Output: Nothing. + * Activates an existing alarm or creates a new one. + *

    + * 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. + *

    + *

    Request parameters

    + *
      + *
    • {@link #EXTRA_HOUR} (optional): The hour of the alarm being set. + *
    • {@link #EXTRA_MINUTES} (optional): The minutes of the alarm being set. + *
    • {@link #EXTRA_DAYS} (optional): Weekdays for repeating alarm. + *
    • {@link #EXTRA_MESSAGE} (optional): A custom message for the alarm. + *
    • {@link #EXTRA_RINGTONE} (optional): A ringtone to play with this alarm. + *
    • {@link #EXTRA_VIBRATE} (optional): Whether or not to activate the device + * vibrator for this alarm. + *
    • {@link #EXTRA_SKIP_UI} (optional): Whether or not to display an activity for + * setting this alarm. + *
    */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SET_ALARM = "android.intent.action.SET_ALARM"; /** - * Activity Extra: Provide a custom message for the alarm. + * Activity Action: Set a timer. *

    - * This can be passed as an extra field in the Intent created with - * ACTION_SET_ALARM. + * Activates an existing timer or creates a new one. + *

    + * This action requests a timer to be started for a specific {@link #EXTRA_LENGTH length} of + * 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. + *

    + * + *

    Request parameters

    + *
      + *
    • {@link #EXTRA_LENGTH} (optional): The length of the timer being set. + *
    • {@link #EXTRA_MESSAGE} (optional): A custom message for the timer. + *
    • {@link #EXTRA_SKIP_UI} (optional): Whether or not to display an activity for + * setting this timer. + *
    */ - public static final String EXTRA_MESSAGE = "android.intent.extra.alarm.MESSAGE"; + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SET_TIMER = "android.intent.action.SET_TIMER"; + + /** + * Activity Action: Show the alarms. + *

    + * This action opens the alarms page. + *

    + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SHOW_ALARMS = "android.intent.action.SHOW_ALARMS"; + + /** + * Bundle extra: Weekdays for repeating alarm. + *

    + * Used by {@link #ACTION_SET_ALARM}. + *

    + * The value is an {@code ArrayList}. Each item can be: + *

    + *
      + *
    • {@link java.util.Calendar#SUNDAY}, + *
    • {@link java.util.Calendar#MONDAY}, + *
    • {@link java.util.Calendar#TUESDAY}, + *
    • {@link java.util.Calendar#WEDNESDAY}, + *
    • {@link java.util.Calendar#THURSDAY}, + *
    • {@link java.util.Calendar#FRIDAY}, + *
    • {@link java.util.Calendar#SATURDAY} + *
    + */ + public static final String EXTRA_DAYS = "android.intent.extra.alarm.DAYS"; /** - * Activity Extra: The hour of the alarm being set. + * Bundle extra: The hour of the alarm. *

    - * This value can be passed as an extra field to the Intent created with - * ACTION_SET_ALARM. If it is not provided, the behavior is undefined and - * is up to the application. The value is an integer and ranges from 0 to - * 23. + * Used by {@link #ACTION_SET_ALARM}. + *

    + * This extra is optional. If not provided, an implementation should open an activity + * that allows a user to set an alarm with user provided time. + *

    + * The value is an {@link Integer} and ranges from 0 to 23. + *

    + * + * @see #ACTION_SET_ALARM + * @see #EXTRA_MINUTES + * @see #EXTRA_DAYS */ public static final String EXTRA_HOUR = "android.intent.extra.alarm.HOUR"; /** - * Activity Extra: The minutes of the alarm being set. + * Bundle extra: The length of the timer in seconds. + *

    + * Used by {@link #ACTION_SET_TIMER}. + *

    + * This extra is optional. If not provided, an implementation should open an activity + * that allows a user to set a timer with user provided length. + *

    + * The value is an {@link Integer} and ranges from 1 to 86400 (24 hours). + *

    + * + * @see #ACTION_SET_TIMER + */ + public static final String EXTRA_LENGTH = "android.intent.extra.alarm.LENGTH"; + + /** + * Bundle extra: A custom message for the alarm or timer. + *

    + * Used by {@link #ACTION_SET_ALARM} and {@link #ACTION_SET_TIMER}. + *

    + * The value is a {@link String}. + *

    + * + * @see #ACTION_SET_ALARM + * @see #ACTION_SET_TIMER + */ + public static final String EXTRA_MESSAGE = "android.intent.extra.alarm.MESSAGE"; + + /** + * Bundle extra: The minutes of the alarm. *

    - * This value can be passed as an extra field to the Intent created with - * ACTION_SET_ALARM. If it is not provided, the behavior is undefined and - * is up to the application. The value is an integer and ranges from 0 to - * 59. + * Used by {@link #ACTION_SET_ALARM}. + *

    + * The value is an {@link Integer} and ranges from 0 to 59. If not provided, it defaults to 0. + *

    + * + * @see #ACTION_SET_ALARM + * @see #EXTRA_HOUR + * @see #EXTRA_DAYS */ public static final String EXTRA_MINUTES = "android.intent.extra.alarm.MINUTES"; /** - * Activity Extra: Optionally skip the application UI. + * Bundle extra: A ringtone to be played with this alarm. + *

    + * Used by {@link #ACTION_SET_ALARM}. + *

    + * This value is a {@link String} and can either be set to {@link #VALUE_RINGTONE_SILENT} or + * to a content URI of the media to be played. If not specified or the URI doesn't exist, + * {@code "content://settings/system/alarm_alert} will be used. + *

    + * + * @see #ACTION_SET_ALARM + * @see #VALUE_RINGTONE_SILENT + * @see #EXTRA_VIBRATE + */ + public static final String EXTRA_RINGTONE = "android.intent.extra.alarm.RINGTONE"; + + /** + * Bundle extra: Whether or not to display an activity after performing the action. *

    - * This value can be passed as an extra field to the Intent created with - * ACTION_SET_ALARM. If true, the application is asked to bypass any - * intermediate UI and instead pop a toast indicating the result then - * finish the Activity. If false, the application may display intermediate - * UI like a confirmation dialog or alarm settings. The default is false. + * Used by {@link #ACTION_SET_ALARM} and {@link #ACTION_SET_TIMER}. + *

    + * If true, the application is asked to bypass any intermediate UI. If false, the application + * may display intermediate UI like a confirmation dialog or settings. + *

    + * The value is a {@link Boolean}. The default is {@code false}. + *

    + * + * @see #ACTION_SET_ALARM + * @see #ACTION_SET_TIMER */ public static final String EXTRA_SKIP_UI = "android.intent.extra.alarm.SKIP_UI"; + + /** + * Bundle extra: Whether or not to activate the device vibrator. + *

    + * Used by {@link #ACTION_SET_ALARM}. + *

    + * The value is a {@link Boolean}. The default is {@code true}. + *

    + * + * @see #ACTION_SET_ALARM + * @see #EXTRA_RINGTONE + * @see #VALUE_RINGTONE_SILENT + */ + public static final String EXTRA_VIBRATE = "android.intent.extra.alarm.VIBRATE"; + + /** + * Bundle extra value: Indicates no ringtone should be played. + *

    + * Used by {@link #ACTION_SET_ALARM}, passed in through {@link #EXTRA_RINGTONE}. + *

    + * + * @see #ACTION_SET_ALARM + * @see #EXTRA_RINGTONE + * @see #EXTRA_VIBRATE + */ + public static final String VALUE_RINGTONE_SILENT = "silent"; } diff --git a/core/java/android/provider/Applications.java b/core/java/android/provider/Applications.java deleted file mode 100644 index 7aabc5080928a3c0e3a6217c8223266c281925d4..0000000000000000000000000000000000000000 --- a/core/java/android/provider/Applications.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2009 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 android.content.ComponentName; -import android.content.ContentResolver; -import android.database.Cursor; -import android.net.Uri; - -import java.util.List; - -/** - * The Applications provider gives information about installed applications. - * - * @hide Only used by ApplicationsProvider so far. - */ -public class Applications { - - /** - * The content authority for this provider. - */ - public static final String AUTHORITY = "applications"; - - /** - * The content:// style URL for this provider - */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); - - /** - * The content path for application component URIs. - */ - public static final String APPLICATION_PATH = "applications"; - - /** - * The content path for application search. - */ - public static final String SEARCH_PATH = "search"; - - private static final String APPLICATION_SUB_TYPE = "vnd.android.application"; - - /** - * The MIME type for a single application item. - */ - public static final String APPLICATION_ITEM_TYPE = - ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + APPLICATION_SUB_TYPE; - - /** - * The MIME type for a list of application items. - */ - public static final String APPLICATION_DIR_TYPE = - ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + APPLICATION_SUB_TYPE; - - /** - * no public constructor since this is a utility class - */ - private Applications() {} - - /** - * Gets a cursor with application search results. - * See {@link ApplicationColumns} for the columns available in the returned cursor. - */ - public static Cursor search(ContentResolver resolver, String query) { - Uri searchUri = CONTENT_URI.buildUpon().appendPath(SEARCH_PATH).appendPath(query).build(); - return resolver.query(searchUri, null, null, null, null); - } - - /** - * Gets the application component name from an application URI. - * - * @param appUri A URI of the form - * "content://applications/applications/<packageName>/<className>". - * @return The component name for the application, or - * null if the given URI was null - * or malformed. - */ - public static ComponentName uriToComponentName(Uri appUri) { - if (appUri == null) return null; - if (!ContentResolver.SCHEME_CONTENT.equals(appUri.getScheme())) return null; - if (!AUTHORITY.equals(appUri.getAuthority())) return null; - List pathSegments = appUri.getPathSegments(); - if (pathSegments.size() != 3) return null; - if (!APPLICATION_PATH.equals(pathSegments.get(0))) return null; - String packageName = pathSegments.get(1); - String name = pathSegments.get(2); - return new ComponentName(packageName, name); - } - - /** - * Gets the URI for an application component. - * - * @param packageName The name of the application's package. - * @param className The class name of the application. - * @return A URI of the form - * "content://applications/applications/<packageName>/<className>". - */ - public static Uri componentNameToUri(String packageName, String className) { - return Applications.CONTENT_URI.buildUpon() - .appendEncodedPath(APPLICATION_PATH) - .appendPath(packageName) - .appendPath(className) - .build(); - } - - /** - * The columns in application cursors, like those returned by - * {@link Applications#search(ContentResolver, String)}. - */ - public interface ApplicationColumns extends BaseColumns { - public static final String NAME = "name"; - public static final String ICON = "icon"; - public static final String URI = "uri"; - } -} diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index 25af209581f6b34fd9af7927c1f1e4c6d7a18392..d7434843641d891d840b974859095e2e579cf127 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -2393,7 +2393,7 @@ public final class CalendarContract { intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime)); intent.putExtra(ALARM_TIME, alarmTime); PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0); - manager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi); + manager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, pi); } /** diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 5dca67ff6f989504d9e45cf8d52dfc2bdcb1fe9b..a6f23a853e2dc09194020e1ca77fc0be2b257c6a 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -136,6 +136,32 @@ public class CallLog { */ public static final String NUMBER = "number"; + /** + * The number presenting rules set by the network. + * + *

    + * Allowed values: + *

      + *
    • {@link #PRESENTATION_ALLOWED}
    • + *
    • {@link #PRESENTATION_RESTRICTED}
    • + *
    • {@link #PRESENTATION_UNKNOWN}
    • + *
    • {@link #PRESENTATION_PAYPHONE}
    • + *
    + *

    + * + *

    Type: INTEGER

    + */ + public static final String NUMBER_PRESENTATION = "presentation"; + + /** Number is allowed to display for caller id. */ + public static final int PRESENTATION_ALLOWED = 1; + /** Number is blocked by user. */ + public static final int PRESENTATION_RESTRICTED = 2; + /** Number is not specified or unknown by network. */ + public static final int PRESENTATION_UNKNOWN = 3; + /** Number is a pay phone. */ + public static final int PRESENTATION_PAYPHONE = 4; + /** * The ISO 3166-1 two letters country code of the country where the * user received or made the call. @@ -267,7 +293,8 @@ public class CallLog { * if the contact is unknown. * @param context the context used to get the ContentResolver * @param number the phone number to be added to the calls db - * @param presentation the number presenting rules set by the network for + * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which + * is set by the network and denotes the number presenting rules for * "allowed", "payphone", "restricted" or "unknown" * @param callType enumerated values for "incoming", "outgoing", or "missed" * @param start time stamp for the call in milliseconds @@ -278,24 +305,32 @@ public class CallLog { public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, long start, int duration) { final ContentResolver resolver = context.getContentResolver(); + int numberPresentation = PRESENTATION_ALLOWED; - // If this is a private number then set the number to Private, otherwise check - // if the number field is empty and set the number to Unavailable + // Remap network specified number presentation types + // PhoneConstants.PRESENTATION_xxx to calllog number presentation types + // Calls.PRESENTATION_xxx, in order to insulate the persistent calllog + // from any future radio changes. + // If the number field is empty set the presentation type to Unknown. if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { - number = CallerInfo.PRIVATE_NUMBER; - if (ci != null) ci.name = ""; + numberPresentation = PRESENTATION_RESTRICTED; } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { - number = CallerInfo.PAYPHONE_NUMBER; - if (ci != null) ci.name = ""; + numberPresentation = PRESENTATION_PAYPHONE; } else if (TextUtils.isEmpty(number) || presentation == PhoneConstants.PRESENTATION_UNKNOWN) { - number = CallerInfo.UNKNOWN_NUMBER; - if (ci != null) ci.name = ""; + numberPresentation = PRESENTATION_UNKNOWN; + } + if (numberPresentation != PRESENTATION_ALLOWED) { + number = ""; + if (ci != null) { + ci.name = ""; + } } - ContentValues values = new ContentValues(5); + ContentValues values = new ContentValues(6); values.put(NUMBER, number); + values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation)); values.put(TYPE, Integer.valueOf(callType)); values.put(DATE, Long.valueOf(start)); values.put(DURATION, Long.valueOf(duration)); diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 220b99717aa3b6587bfd8fb76ea197d34fc3062b..b16df28fbb3fbe7cdf2093d59c7e7cd94dba3012 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -824,6 +824,14 @@ public final class ContactsContract { */ public static final String STARRED = "starred"; + /** + * The position at which the contact is pinned. If {@link PinnedPositions.UNPINNED}, + * the contact is not pinned. Also see {@link PinnedPositions}. + *

    Type: INTEGER

    + * @hide + */ + public static final String PINNED = "pinned"; + /** * URI for a custom ringtone associated with the contact. If null or missing, * the default ringtone is used. @@ -5105,9 +5113,8 @@ public final class ContactsContract { * Value of 1 implies true, 0 implies false when 0 is the default. * When a cursor is returned to the client, it should check for an extra with the name * {@link ContactsContract#DEFERRED_SNIPPETING} in the cursor. If it exists, the client - * should do its own snippeting using {@link ContactsContract#snippetize}. If - * it doesn't exist, the snippet column in the cursor should already contain a snippetized - * string. + * should do its own snippeting. If it doesn't exist, the snippet column in the cursor + * should already contain a snippetized string. * * @hide */ @@ -7714,6 +7721,138 @@ public final class ContactsContract { public static final String USAGE_TYPE_SHORT_TEXT = "short_text"; } + /** + *

    + * API allowing applications to send pinning information for specified contacts to the + * Contacts Provider. + *

    + * + *

    + * This pinning information can be used by individual applications to customize how + * they order particular pinned contacts. For example, a Dialer application could + * use pinned information to order user-pinned contacts in a top row of favorites. + *

    + * + *

    + * It is possible for two or more contacts to occupy the same pinned position (due + * to aggregation and sync), so this pinning information should be used on a best-effort + * basis to order contacts in-application rather than an absolute guide on where a contact + * should be positioned. Contacts returned by the ContactsProvider will not be ordered based + * on this information, so it is up to the client application to reorder these contacts within + * their own UI adhering to (or ignoring as appropriate) information stored in the pinned + * column. + *

    + * + *

    + * By default, unpinned contacts will have a pinned position of + * {@link PinnedPositions#UNPINNED}, or {@link Integer#MAX_VALUE} (2^31 - 1). Client-provided + * pinned positions can be positive integers that range anywhere from 0 to + * {@link PinnedPositions#UNPINNED}. + *

    + * + *

    + * When using {@link PinnedPositions#UPDATE_URI} to update the pinned positions of + * certain contacts, it may make sense for your application to star any pinned contacts + * by default. To specify this behavior, set the boolean query parameter + * {@link PinnedPositions#STAR_WHEN_PINNING} to true to force all pinned and unpinned + * contacts to be automatically starred and unstarred. + *

    + * @hide + */ + public static final class PinnedPositions { + + /** + *

    + * This URI allows applications to update pinned positions for a provided set of contacts. + *

    + * + *

    + * The list of contactIds to pin and their corresponding pinned positions should be + * provided in key-value pairs stored in a {@link ContentValues} object where the key + * is a valid contactId, while each pinned position is a positive integer. + *

    + * + *

    + * Example: + *

    +         * ContentValues values = new ContentValues();
    +         * values.put("10", 20);
    +         * values.put("12", 2);
    +         * values.put("15", PinnedPositions.UNPINNED);
    +         * int count = resolver.update(PinnedPositions.UPDATE_URI, values, null, null);
    +         * 
    + * + * This pins the contact with id 10 at position 20, the contact with id 12 at position 2, + * and unpins the contact with id 15. + *

    + */ + public static final Uri UPDATE_URI = Uri.withAppendedPath(AUTHORITY_URI, + "pinned_position_update"); + + /** + * Default value for the pinned position of an unpinned contact. Also equal to + * {@link Integer#MAX_VALUE}. + */ + public static final int UNPINNED = 0x7FFFFFFF; + + /** + * Value of pinned position for a contact that a user has indicated should be considered + * of the lowest priority. It is up to the client application to determine how to present + * such a contact - for example all the way at the bottom of a contact list, or simply + * just hidden from view. + */ + public static final int DEMOTED = -1; + + /** + *

    Clients can provide this value as a pinned position to undemote a formerly demoted + * contact. If the contact was formerly demoted, it will be restored to an + * {@link #UNPINNED} position. If it was otherwise already pinned at another position, + * it will not be affected. + *

    + * + *

    + * Example: + *

    +         * ContentValues values = new ContentValues();
    +         * values.put("15", PinnedPositions.UNDEMOTE);
    +         * int count = resolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon()
    +         *          .build(), values, null, null);
    +         * 
    + * + * This restores the contact with id 15 to an {@link #UNPINNED} position, meaning that + * other apps (e.g. the Dialer) that were formerly hiding this contact from view based on + * its {@link #DEMOTED} position will start displaying it again. + *

    + */ + public static final String UNDEMOTE = "undemote"; + + /** + *

    + * A boolean query parameter that can be used with {@link #UPDATE_URI}. + * If "1" or "true", any contact that is pinned or unpinned will be correspondingly + * starred or unstarred. Otherwise, starring information will not be affected by pinned + * updates. This is false by default. + *

    + * + *

    + * Example: + *

    +         * ContentValues values = new ContentValues();
    +         * values.put("10", 20);
    +         * values.put("15", PinnedPositions.UNPINNED);
    +         * int count = resolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon()
    +         *          .appendQueryParameter(PinnedPositions.FORCE_STAR_WHEN_PINNING, "true").build(),
    +         *          values, null, null);
    +         * 
    + * + * This will pin the contact with id 10 at position 20 and star it automatically if not + * already starred, and unpin the contact with id 15, and unstar it automatically if not + * already unstarred. + *

    + */ + public static final String STAR_WHEN_PINNING = "star_when_pinning"; + } + /** * Helper methods to display QuickContact dialogs that allow users to pivot on * a specific {@link Contacts} entry. @@ -8463,138 +8602,4 @@ public final class ContactsContract { public static final String DATA_SET = "com.android.contacts.extra.DATA_SET"; } } - - /** - * Creates a snippet out of the given content that matches the given query. - * @param content - The content to use to compute the snippet. - * @param displayName - Display name for the contact - if this already contains the search - * content, no snippet should be shown. - * @param query - String to search for in the content. - * @param snippetStartMatch - Marks the start of the matching string in the snippet. - * @param snippetEndMatch - Marks the end of the matching string in the snippet. - * @param snippetEllipsis - Ellipsis string appended to the end of the snippet (if too long). - * @param snippetMaxTokens - Maximum number of words from the snippet that will be displayed. - * @return The computed snippet, or null if the snippet could not be computed or should not be - * shown. - * - * @hide - */ - public static String snippetize(String content, String displayName, String query, - char snippetStartMatch, char snippetEndMatch, String snippetEllipsis, - int snippetMaxTokens) { - - String lowerQuery = query != null ? query.toLowerCase() : null; - if (TextUtils.isEmpty(content) || TextUtils.isEmpty(query) || - TextUtils.isEmpty(displayName) || !content.toLowerCase().contains(lowerQuery)) { - return null; - } - - // If the display name already contains the query term, return empty - snippets should - // not be needed in that case. - String lowerDisplayName = displayName != null ? displayName.toLowerCase() : ""; - List nameTokens = new ArrayList(); - List nameTokenOffsets = new ArrayList(); - split(lowerDisplayName.trim(), nameTokens, nameTokenOffsets); - for (String nameToken : nameTokens) { - if (nameToken.startsWith(lowerQuery)) { - return null; - } - } - - String[] contentLines = content.split("\n"); - - // Locate the lines of the content that contain the query term. - for (String contentLine : contentLines) { - if (contentLine.toLowerCase().contains(lowerQuery)) { - - // Line contains the query string - now search for it at the start of tokens. - List lineTokens = new ArrayList(); - List tokenOffsets = new ArrayList(); - split(contentLine, lineTokens, tokenOffsets); - - // As we find matches against the query, we'll populate this list with the marked - // (or unchanged) tokens. - List markedTokens = new ArrayList(); - - int firstToken = -1; - int lastToken = -1; - for (int i = 0; i < lineTokens.size(); i++) { - String token = lineTokens.get(i); - String lowerToken = token.toLowerCase(); - if (lowerToken.startsWith(lowerQuery)) { - - // Query term matched; surround the token with match markers. - markedTokens.add(snippetStartMatch + token + snippetEndMatch); - - // If this is the first token found with a match, mark the token - // positions to use for assembling the snippet. - if (firstToken == -1) { - firstToken = - Math.max(0, i - (int) Math.floor( - Math.abs(snippetMaxTokens) - / 2.0)); - lastToken = - Math.min(lineTokens.size(), firstToken + - Math.abs(snippetMaxTokens)); - } - } else { - markedTokens.add(token); - } - } - - // Assemble the snippet by piecing the tokens back together. - if (firstToken > -1) { - StringBuilder sb = new StringBuilder(); - if (firstToken > 0) { - sb.append(snippetEllipsis); - } - for (int i = firstToken; i < lastToken; i++) { - String markedToken = markedTokens.get(i); - String originalToken = lineTokens.get(i); - sb.append(markedToken); - if (i < lastToken - 1) { - // Add the characters that appeared between this token and the next. - sb.append(contentLine.substring( - tokenOffsets.get(i) + originalToken.length(), - tokenOffsets.get(i + 1))); - } - } - if (lastToken < lineTokens.size()) { - sb.append(snippetEllipsis); - } - return sb.toString(); - } - } - } - return null; - } - - /** - * Pattern for splitting a line into tokens. This matches e-mail addresses as a single token, - * otherwise splitting on any group of non-alphanumeric characters. - * - * @hide - */ - private static Pattern SPLIT_PATTERN = - Pattern.compile("([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+"); - - /** - * Helper method for splitting a string into tokens. The lists passed in are populated with the - * tokens and offsets into the content of each token. The tokenization function parses e-mail - * addresses as a single token; otherwise it splits on any non-alphanumeric character. - * @param content Content to split. - * @param tokens List of token strings to populate. - * @param offsets List of offsets into the content for each token returned. - * - * @hide - */ - private static void split(String content, List tokens, List offsets) { - Matcher matcher = SPLIT_PATTERN.matcher(content); - while (matcher.find()) { - tokens.add(matcher.group()); - offsets.add(matcher.start()); - } - } - - } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java new file mode 100644 index 0000000000000000000000000000000000000000..7f8dca2f42408a2cbe1f10890e0f7dcdd8c560a9 --- /dev/null +++ b/core/java/android/provider/DocumentsContract.java @@ -0,0 +1,854 @@ +/* + * 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.net.TrafficStats.KB_IN_BYTES; +import static libcore.io.OsConstants.SEEK_SET; + +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.graphics.Point; +import android.media.ExifInterface; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.OnCloseListener; +import android.os.RemoteException; +import android.util.Log; + +import libcore.io.ErrnoException; +import libcore.io.IoUtils; +import libcore.io.Libcore; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +/** + * Defines the contract between a documents provider and the platform. + *

    + * To create a document provider, extend {@link DocumentsProvider}, which + * provides a foundational implementation of this contract. + * + * @see DocumentsProvider + */ +public final class DocumentsContract { + private static final String TAG = "Documents"; + + // content://com.example/root/ + // content://com.example/root/sdcard/ + // content://com.example/root/sdcard/recent/ + // content://com.example/root/sdcard/search/?query=pony + // content://com.example/document/12/ + // content://com.example/document/12/children/ + + private DocumentsContract() { + } + + /** + * Intent action used to identify {@link DocumentsProvider} instances. This + * is used in the {@code } of a {@code }. + */ + public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; + + /** {@hide} */ + public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME"; + + /** + * Included in {@link AssetFileDescriptor#getExtras()} when returned + * thumbnail should be rotated. + * + * @see MediaStore.Images.ImageColumns#ORIENTATION + * @hide + */ + public static final String EXTRA_ORIENTATION = "android.content.extra.ORIENTATION"; + + /** {@hide} */ + public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT"; + /** {@hide} */ + public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; + + /** + * Buffer is large enough to rewind past any EXIF headers. + */ + private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES); + + /** + * Constants related to a document, including {@link Cursor} column names + * and flags. + *

    + * A document can be either an openable stream (with a specific MIME type), + * or a directory containing additional documents (with the + * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a + * subtree containing zero or more documents, which can recursively contain + * even more documents and directories. + *

    + * All columns are read-only to client applications. + */ + public final static class Document { + private Document() { + } + + /** + * 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. This column is required. + *

    + * Each document must have a unique ID within a provider, but that + * single document may be included as a child of multiple directories. + *

    + * 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}. + *

    + * 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. This column is required. + *

    + * Type: STRING + * + * @see #MIME_TYPE_DIR + */ + public static final String COLUMN_MIME_TYPE = "mime_type"; + + /** + * Display name of a document, used as the primary title displayed to a + * user. This column is required. + *

    + * Type: STRING + */ + public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME; + + /** + * Summary of a document, which may be shown to a user. This column is + * optional, and may be {@code null}. + *

    + * 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. This column is required, and may be + * {@code null} if unknown. A {@link DocumentsProvider} can update this + * field using events from {@link OnCloseListener} or other reliable + * {@link ParcelFileDescriptor} transports. + *

    + * Type: INTEGER (long) + * + * @see System#currentTimeMillis() + */ + public static final String COLUMN_LAST_MODIFIED = "last_modified"; + + /** + * Specific icon resource ID for a document. This column is optional, + * and may be {@code null} to use a platform-provided default icon based + * on {@link #COLUMN_MIME_TYPE}. + *

    + * Type: INTEGER (int) + */ + public static final String COLUMN_ICON = "icon"; + + /** + * Flags that apply to a document. This column is required. + *

    + * Type: INTEGER (int) + * + * @see #FLAG_SUPPORTS_WRITE + * @see #FLAG_SUPPORTS_DELETE + * @see #FLAG_SUPPORTS_THUMBNAIL + * @see #FLAG_DIR_PREFERS_GRID + * @see #FLAG_DIR_PREFERS_LAST_MODIFIED + */ + public static final String COLUMN_FLAGS = "flags"; + + /** + * Size of a document, in bytes, or {@code null} if unknown. This column + * is required. + *

    + * 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 #COLUMN_MIME_TYPE + */ + public static final String MIME_TYPE_DIR = "vnd.android.document/directory"; + + /** + * Flag indicating that a document can be represented as a thumbnail. + * + * @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; + + /** + * Flag indicating that a document supports writing. + *

    + * 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 #COLUMN_FLAGS + */ + public static final int FLAG_SUPPORTS_WRITE = 1 << 1; + + /** + * Flag indicating that a document is deletable. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#deleteDocument(ContentResolver, Uri) + * @see DocumentsProvider#deleteDocument(String) + */ + public static final int FLAG_SUPPORTS_DELETE = 1 << 2; + + /** + * 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 #COLUMN_FLAGS + * @see DocumentsContract#createDocument(ContentResolver, Uri, String, + * String) + * @see DocumentsProvider#createDocument(String, String, String) + */ + public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3; + + /** + * 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 << 4; + + /** + * Flag indicating that a directory prefers its contents be sorted by + * {@link #COLUMN_LAST_MODIFIED}. Only valid when + * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + */ + public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; + + /** + * Flag indicating that document titles should be hidden when viewing + * this directory in a larger format grid. For example, a directory + * containing only images may want the image thumbnails to speak for + * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is + * {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + * @see #FLAG_DIR_PREFERS_GRID + * @hide + */ + public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16; + } + + /** + * Constants related to a root of documents, including {@link Cursor} column + * names and flags. A root is the start of a tree of documents, such as a + * physical storage device, or an account. Each root starts at the directory + * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively + * contain both documents and directories. + *

    + * All columns are read-only to client applications. + */ + public final static class Root { + private Root() { + } + + /** + * 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. This column is required. + *

    + * Type: STRING + */ + public static final String COLUMN_ROOT_ID = "root_id"; + + /** + * Flags that apply to a root. This column is required. + *

    + * Type: INTEGER (int) + * + * @see #FLAG_LOCAL_ONLY + * @see #FLAG_SUPPORTS_CREATE + * @see #FLAG_SUPPORTS_RECENTS + * @see #FLAG_SUPPORTS_SEARCH + */ + public static final String COLUMN_FLAGS = "flags"; + + /** + * Icon resource ID for a root. This column is required. + *

    + * Type: INTEGER (int) + */ + public static final String COLUMN_ICON = "icon"; + + /** + * Title for a root, which will be shown to a user. This column is + * required. For a single storage service surfacing multiple accounts as + * different roots, this title should be the name of the service. + *

    + * Type: STRING + */ + public static final String COLUMN_TITLE = "title"; + + /** + * Summary for this root, which may be shown to a user. This column is + * optional, and may be {@code null}. For a single storage service + * surfacing multiple accounts as different roots, this summary should + * be the name of the account. + *

    + * Type: STRING + */ + public static final String COLUMN_SUMMARY = "summary"; + + /** + * Document which is a directory that represents the top directory of + * this root. This column is required. + *

    + * Type: STRING + * + * @see Document#COLUMN_DOCUMENT_ID + */ + public static final String COLUMN_DOCUMENT_ID = "document_id"; + + /** + * Number of bytes available in this root. This column is optional, and + * may be {@code null} if unknown or unbounded. + *

    + * Type: INTEGER (long) + */ + public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; + + /** + * MIME types supported by this root. This column is optional, and if + * {@code null} the root is assumed to support all MIME types. Multiple + * MIME types can be separated by a newline. For example, a root + * supporting audio might return "audio/*\napplication/x-flac". + *

    + * Type: STRING + */ + public static final String COLUMN_MIME_TYPES = "mime_types"; + + /** {@hide} */ + public static final String MIME_TYPE_ITEM = "vnd.android.document/root"; + + /** + * 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 #COLUMN_FLAGS + */ + public static final int FLAG_SUPPORTS_CREATE = 1; + + /** + * 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; + + /** + * Flag indicating that this root can be queried to provide recently + * modified documents. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#buildRecentDocumentsUri(String, String) + * @see DocumentsProvider#queryRecentDocuments(String, String[]) + */ + public static final int FLAG_SUPPORTS_RECENTS = 1 << 2; + + /** + * Flag indicating that this root supports search. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#buildSearchDocumentsUri(String, String, + * String) + * @see DocumentsProvider#querySearchDocuments(String, String, + * String[]) + */ + public static final int FLAG_SUPPORTS_SEARCH = 1 << 3; + + /** + * Flag indicating that this root is currently empty. This may be used + * to hide the root when opening documents, but the root will still be + * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is + * also set. If the value of this flag changes, such as when a root + * becomes non-empty, you must send a content changed notification for + * {@link DocumentsContract#buildRootsUri(String)}. + * + * @see #COLUMN_FLAGS + * @see ContentResolver#notifyChange(Uri, + * android.database.ContentObserver, boolean) + * @hide + */ + public static final int FLAG_EMPTY = 1 << 16; + + /** + * Flag indicating that this root should only be visible to advanced + * users. + * + * @see #COLUMN_FLAGS + * @hide + */ + public static final int FLAG_ADVANCED = 1 << 17; + } + + /** + * 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"; + private static final String PARAM_MANAGE = "manage"; + + /** + * 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 given {@link Root#COLUMN_ROOT_ID} in a + * document provider. + * + * @see #getRootId(Uri) + */ + public static Uri buildRootUri(String authority, String rootId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build(); + } + + /** + * Build URI representing the recently modified documents of a specific root + * in a document provider. 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 + * root in a document provider. When queried, a provider will return zero or + * more rows with columns defined by {@link Document}. + * + * @see DocumentsProvider#querySearchDocuments(String, String, String[]) + * @see #getRootId(Uri) + * @see #getSearchDocumentsQuery(Uri) + */ + public static Uri buildSearchDocumentsUri( + String authority, String rootId, String query) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH) + .appendQueryParameter(PARAM_QUERY, query).build(); + } + + /** + * Test if the given URI represents a {@link Document} backed by a + * {@link DocumentsProvider}. + */ + public static boolean isDocumentUri(Context context, Uri uri) { + final List paths = uri.getPathSegments(); + if (paths.size() < 2) { + return false; + } + if (!PATH_DOCUMENT.equals(paths.get(0))) { + return false; + } + + final Intent intent = new Intent(PROVIDER_INTERFACE); + final List infos = context.getPackageManager() + .queryIntentContentProviders(intent, 0); + for (ResolveInfo info : infos) { + if (uri.getAuthority().equals(info.providerInfo.authority)) { + return true; + } + } + return false; + } + + /** + * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI. + */ + public static String getRootId(Uri rootUri) { + final List 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 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); + } + + /** {@hide} */ + public static Uri setManageMode(Uri uri) { + return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); + } + + /** {@hide} */ + public static boolean isManageMode(Uri uri) { + return uri.getBooleanQueryParameter(PARAM_MANAGE, false); + } + + /** + * 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 if 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 getDocumentThumbnail( + ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + documentUri.getAuthority()); + try { + return getDocumentThumbnail(client, documentUri, size, signal); + } catch (Exception e) { + Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); + return null; + } finally { + ContentProviderClient.releaseQuietly(client); + } + } + + /** {@hide} */ + public static Bitmap getDocumentThumbnail( + ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) + throws RemoteException, IOException { + final Bundle openOpts = new Bundle(); + openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); + + AssetFileDescriptor afd = null; + Bitmap bitmap = null; + try { + afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); + + final FileDescriptor fd = afd.getFileDescriptor(); + final long offset = afd.getStartOffset(); + + // Try seeking on the returned FD, since it gives us the most + // optimal decode path; otherwise fall back to buffering. + BufferedInputStream is = null; + try { + Libcore.os.lseek(fd, offset, SEEK_SET); + } catch (ErrnoException e) { + is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE); + is.mark(THUMBNAIL_BUFFER_SIZE); + } + + // We requested a rough thumbnail size, but the remote size may have + // returned something giant, so defensively scale down as needed. + final BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + if (is != null) { + BitmapFactory.decodeStream(is, null, opts); + } else { + BitmapFactory.decodeFileDescriptor(fd, null, opts); + } + + final int widthSample = opts.outWidth / size.x; + final int heightSample = opts.outHeight / size.y; + + opts.inJustDecodeBounds = false; + opts.inSampleSize = Math.min(widthSample, heightSample); + if (is != null) { + is.reset(); + bitmap = BitmapFactory.decodeStream(is, null, opts); + } else { + try { + Libcore.os.lseek(fd, offset, SEEK_SET); + } catch (ErrnoException e) { + e.rethrowAsIOException(); + } + bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts); + } + + // Transform the bitmap if requested. We use a side-channel to + // communicate the orientation, since EXIF thumbnails don't contain + // the rotation flags of the original image. + final Bundle extras = afd.getExtras(); + final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0; + if (orientation != 0) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + + final Matrix m = new Matrix(); + m.setRotate(orientation, width / 2, height / 2); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); + } + } finally { + IoUtils.closeQuietly(afd); + } + + return bitmap; + } + + /** + * Create a new document with given MIME type and display name. + * + * @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 + * @hide + */ + public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, + String mimeType, String displayName) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + parentDocumentUri.getAuthority()); + try { + return createDocument(client, parentDocumentUri, mimeType, displayName); + } catch (Exception e) { + Log.w(TAG, "Failed to create document", e); + return null; + } finally { + ContentProviderClient.releaseQuietly(client); + } + } + + /** {@hide} */ + public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, + String mimeType, String displayName) throws RemoteException { + 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); + + final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); + return buildDocumentUri( + parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); + } + + /** + * Delete the given document. + * + * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} + * @return if the document was deleted successfully. + */ + public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + documentUri.getAuthority()); + try { + deleteDocument(client, documentUri); + return true; + } catch (Exception e) { + Log.w(TAG, "Failed to delete document", e); + return false; + } finally { + ContentProviderClient.releaseQuietly(client); + } + } + + /** {@hide} */ + public static void deleteDocument(ContentProviderClient client, Uri documentUri) + throws RemoteException { + final Bundle in = new Bundle(); + in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); + + client.call(METHOD_DELETE_DOCUMENT, null, in); + } + + /** + * Open the given image for thumbnail purposes, using any embedded EXIF + * thumbnail if available, and providing orientation hints from the parent + * image. + * + * @hide + */ + public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException { + final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( + file, ParcelFileDescriptor.MODE_READ_ONLY); + Bundle extras = null; + + try { + final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); + + switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) { + case ExifInterface.ORIENTATION_ROTATE_90: + extras = new Bundle(1); + extras.putInt(EXTRA_ORIENTATION, 90); + break; + case ExifInterface.ORIENTATION_ROTATE_180: + extras = new Bundle(1); + extras.putInt(EXTRA_ORIENTATION, 180); + break; + case ExifInterface.ORIENTATION_ROTATE_270: + extras = new Bundle(1); + extras.putInt(EXTRA_ORIENTATION, 270); + break; + } + + final long[] thumb = exif.getThumbnailRange(); + if (thumb != null) { + return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras); + } + } catch (IOException e) { + } + + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); + } +} diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..c9efb53a959d9b9652e30107ce3c4b74b4516c6f --- /dev/null +++ b/core/java/android/provider/DocumentsProvider.java @@ -0,0 +1,592 @@ +/* + * 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.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.UriMatcher; +import android.content.pm.PackageManager; +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.provider.DocumentsContract.Root; +import android.util.Log; + +import libcore.io.IoUtils; + +import java.io.FileNotFoundException; + +/** + * Base class for a document provider. A document provider offers read and write + * access to durable files, such as files stored on a local disk, or files in a + * cloud storage service. To create a document provider, extend this class, + * implement the abstract methods, and add it to your manifest like this: + * + *

    <manifest>
    + *    ...
    + *    <application>
    + *        ...
    + *        <provider
    + *            android:name="com.example.MyCloudProvider"
    + *            android:authorities="com.example.mycloudprovider"
    + *            android:exported="true"
    + *            android:grantUriPermissions="true"
    + *            android:permission="android.permission.MANAGE_DOCUMENTS">
    + *            <intent-filter>
    + *                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
    + *            </intent-filter>
    + *        </provider>
    + *        ...
    + *    </application>
    + *</manifest>
    + *

    + * When defining your provider, you must protect it with + * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission + * only the system can obtain. Applications cannot use a documents provider + * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or + * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively + * navigate and select documents. When a user selects documents through that + * UI, the system issues narrow URI permission grants to the requesting + * application. + *

    + *

    Documents

    + *

    + * A document can be either an openable stream (with a specific MIME type), or a + * directory containing additional documents (with the + * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top + * of a subtree containing zero or more documents, which can recursively contain + * even more documents and directories. + *

    + *

    + * Each document can have different capabilities, as described by + * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented + * as a thumbnail, a provider can set {@link Document#FLAG_SUPPORTS_THUMBNAIL} + * and implement + * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return + * that thumbnail. + *

    + *

    + * Each document under a provider is uniquely referenced by its + * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A + * single document can be included in multiple directories when responding to + * {@link #queryChildDocuments(String, String[], String)}. For example, a + * provider might surface a single photo in multiple locations: once in a + * directory of locations, and again in a directory of dates. + *

    + *

    Roots

    + *

    + * All documents are surfaced through one or more "roots." Each root represents + * the top of a document tree that a user can navigate. For example, a root + * could represent an account or a physical storage device. Similar to + * documents, each root can have capabilities expressed through + * {@link Root#COLUMN_FLAGS}. + *

    + * + * @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_ROOTS = 1; + private static final int MATCH_ROOT = 2; + private static final int MATCH_RECENT = 3; + private static final int MATCH_SEARCH = 4; + private static final int MATCH_DOCUMENT = 5; + private static final int MATCH_CHILDREN = 6; + + 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_ROOTS); + mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT); + mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); + mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH); + mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); + mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); + + // 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 newly generated + * {@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 parentDocumentId the parent directory to create the new document + * under. + * @param mimeType the concrete MIME type associated with the new document. + * If the MIME type is not supported, the provider must throw. + * @param displayName the display name of the new document. The provider may + * alter this name to meet any internal constraints, such as + * conflicting names. + */ + @SuppressWarnings("unused") + public String createDocument(String parentDocumentId, String mimeType, String displayName) + throws FileNotFoundException { + throw new UnsupportedOperationException("Create not supported"); + } + + /** + * Delete the requested document. Upon returning, any URI permission grants + * for the requested 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"); + } + + /** + * Return all roots currently provided. A provider must define at least one + * root to display to users, and it should avoid making network requests to + * keep this request fast. + *

    + * Each root is defined by the metadata columns described in {@link Root}, + * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory + * representing a tree of documents to display under that root. + *

    + * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri, + * android.database.ContentObserver)} to notify the system. + * + * @param projection list of {@link Root} columns to put into the cursor. If + * {@code null} all supported columns should be included. + */ + public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; + + /** + * Return recently modified documents under the requested root. This will + * only be called for roots that advertise + * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be + * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and + * limited to only return the 64 most recently modified documents. + * + * @param projection list of {@link Document} columns to put into the + * cursor. If {@code null} all supported columns should be + * included. + * @see DocumentsContract#EXTRA_LOADING + */ + @SuppressWarnings("unused") + public Cursor queryRecentDocuments(String rootId, String[] projection) + throws FileNotFoundException { + throw new UnsupportedOperationException("Recent not supported"); + } + + /** + * Return metadata for the single requested document. A provider should + * avoid making network requests to keep this request fast. + * + * @param documentId the document to return. + * @param projection list of {@link Document} columns to put into the + * cursor. If {@code null} all supported columns should be + * included. + */ + public abstract Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException; + + /** + * Return the children documents contained in the requested directory. This + * must only return immediate descendants, as additional queries will be + * issued to recursively explore the tree. + *

    + * If your provider is cloud-based, and you have some data cached or pinned + * locally, you may return the local data immediately, setting + * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that + * your provider is still fetching additional data. Then, when the network + * data is available, you can call {@link ContentResolver#notifyChange(Uri, + * android.database.ContentObserver)} to trigger a requery and return the + * complete contents. + * + * @param parentDocumentId the directory to return children for. + * @param projection list of {@link Document} columns to put into the + * cursor. If {@code null} all supported columns should be + * included. + * @param sortOrder how to order the rows, formatted as an SQL + * {@code ORDER BY} clause (excluding the ORDER BY itself). + * Passing {@code null} will use the default sort order, which + * may be unordered. This ordering is a hint that can be used to + * prioritize how data is fetched from the network, but UI may + * always enforce a specific ordering. + * @see DocumentsContract#EXTRA_LOADING + * @see DocumentsContract#EXTRA_INFO + * @see DocumentsContract#EXTRA_ERROR + */ + public abstract Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException; + + /** {@hide} */ + @SuppressWarnings("unused") + public Cursor queryChildDocumentsForManage( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { + throw new UnsupportedOperationException("Manage not supported"); + } + + /** + * Return documents that that match the given query under the requested + * root. The returned documents should be sorted by relevance in descending + * order. How documents are matched against the query string is an + * implementation detail left to each provider, but it's suggested that at + * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a + * case-insensitive fashion. + *

    + * Only documents may be returned; directories are not supported in search + * results. + * + * @param rootId the root to search under. + * @param query string to match documents against. + * @param projection list of {@link Document} columns to put into the + * cursor. If {@code null} all supported columns should be + * included. + * @see DocumentsContract#EXTRA_LOADING + * @see DocumentsContract#EXTRA_INFO + * @see DocumentsContract#EXTRA_ERROR + */ + @SuppressWarnings("unused") + public Cursor querySearchDocuments(String rootId, String query, String[] projection) + throws FileNotFoundException { + throw new UnsupportedOperationException("Search not supported"); + } + + /** + * Return concrete MIME type of the requested document. Must match the value + * of {@link Document#COLUMN_MIME_TYPE} for this document. The default + * implementation queries {@link #queryDocument(String, String[])}, so + * providers may choose to override this as an optimization. + */ + 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 documentId 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() + * @see ParcelFileDescriptor#parseMode(String) + */ + public abstract ParcelFileDescriptor openDocument( + String documentId, 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 documentId 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 documentId, 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_ROOTS: + return queryRoots(projection); + case MATCH_RECENT: + return queryRecentDocuments(getRootId(uri), projection); + case MATCH_SEARCH: + return querySearchDocuments( + getRootId(uri), getSearchDocumentsQuery(uri), projection); + case MATCH_DOCUMENT: + return queryDocument(getDocumentId(uri), projection); + case MATCH_CHILDREN: + if (DocumentsContract.isManageMode(uri)) { + return queryChildDocumentsForManage( + getDocumentId(uri), projection, sortOrder); + } else { + return queryChildDocuments(getDocumentId(uri), projection, sortOrder); + } + 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_ROOT: + return DocumentsContract.Root.MIME_TYPE_ITEM; + 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"); + } + + /** + * Implementation is provided by the parent class. Can be overridden to + * provide additional functionality, but subclasses must always + * call the superclass. If the superclass returns {@code null}, the subclass + * may implement custom behavior. + * + * @see #openDocument(String, String, CancellationSignal) + * @see #deleteDocument(String) + */ + @Override + public Bundle call(String method, String arg, Bundle extras) { + final Context context = getContext(); + + if (!method.startsWith("android:")) { + // Let non-platform methods pass through + return super.call(method, arg, extras); + } + + final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID); + final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId); + + // Require that caller can manage requested document + final boolean callerHasManage = + context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS) + == PackageManager.PERMISSION_GRANTED; + if (!callerHasManage) { + 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); + + final String newDocumentId = createDocument(documentId, mimeType, displayName); + out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId); + + // Extend permission grant towards caller if needed + if (!callerHasManage) { + final Uri newDocumentUri = DocumentsContract.buildDocumentUri( + mAuthority, newDocumentId); + context.grantUriPermission(getCallingPackage(), newDocumentUri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + } + + } else if (METHOD_DELETE_DOCUMENT.equals(method)) { + deleteDocument(documentId); + + // Document no longer exists, clean up any grants + context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + + } 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. Cannot be overriden. + * + * @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. Cannot be overriden. + * + * @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. Cannot be overriden. + * + * @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. Cannot be overriden. + * + * @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/Downloads.java b/core/java/android/provider/Downloads.java index 99997605aa3268e8f65f4ea59c6dd9a4fbaabcc2..b2d9b934d79b06d126c5550be00ec727ba00c35a 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -410,6 +410,8 @@ public final class Downloads { /** The column that is used to count retries */ public static final String COLUMN_FAILED_CONNECTIONS = "numfailed"; + public static final String COLUMN_ALLOW_WRITE = "allow_write"; + /** * default value for {@link #COLUMN_LAST_UPDATESRC}. * This value is used when this column's value is not relevant. diff --git a/core/java/android/provider/DrmStore.java b/core/java/android/provider/DrmStore.java deleted file mode 100644 index 34f2f0d254321d4cb724eda2249eb7c5418ae182..0000000000000000000000000000000000000000 --- a/core/java/android/provider/DrmStore.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2008 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 android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.drm.mobile1.DrmRawContent; -import android.drm.mobile1.DrmRights; -import android.drm.mobile1.DrmRightsManager; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.util.Log; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * The DRM provider contains forward locked DRM content. - * - * @hide - */ -public final class DrmStore -{ - private static final String TAG = "DrmStore"; - - public static final String AUTHORITY = "drm"; - - /** - * This is in the Manifest class of the drm provider, but that isn't visible - * in the framework. - */ - private static final String ACCESS_DRM_PERMISSION = "android.permission.ACCESS_DRM"; - - /** - * Fields for DRM database - */ - - public interface Columns extends BaseColumns { - /** - * The data stream for the file - *

    Type: DATA STREAM

    - */ - public static final String DATA = "_data"; - - /** - * The size of the file in bytes - *

    Type: INTEGER (long)

    - */ - public static final String SIZE = "_size"; - - /** - * The title of the file content - *

    Type: TEXT

    - */ - public static final String TITLE = "title"; - - /** - * The MIME type of the file - *

    Type: TEXT

    - */ - public static final String MIME_TYPE = "mime_type"; - - } - - public interface Images extends Columns { - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/images"); - } - - public interface Audio extends Columns { - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/audio"); - } - - /** - * Utility function for inserting a file into the DRM content provider. - * - * @param cr The content resolver to use - * @param file The file to insert - * @param title The title for the content (or null) - * @return uri to the DRM record or null - */ - public static final Intent addDrmFile(ContentResolver cr, File file, String title) { - FileInputStream fis = null; - Intent result = null; - - try { - fis = new FileInputStream(file); - if (title == null) { - title = file.getName(); - int lastDot = title.lastIndexOf('.'); - if (lastDot > 0) { - title = title.substring(0, lastDot); - } - } - result = addDrmFile(cr, fis, title); - } catch (Exception e) { - Log.e(TAG, "pushing file failed", e); - } finally { - try { - if (fis != null) - fis.close(); - } catch (IOException e) { - Log.e(TAG, "IOException in DrmStore.addDrmFile()", e); - } - } - - return result; - } - - /** - * Utility function for inserting a file stream into the DRM content provider. - * - * @param cr The content resolver to use - * @param fis The FileInputStream to insert - * @param title The title for the content (or null) - * @return uri to the DRM record or null - */ - public static final Intent addDrmFile(ContentResolver cr, FileInputStream fis, String title) { - OutputStream os = null; - Intent result = null; - - try { - DrmRawContent content = new DrmRawContent(fis, (int) fis.available(), - DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING); - String mimeType = content.getContentType(); - long size = fis.getChannel().size(); - - DrmRightsManager manager = manager = DrmRightsManager.getInstance(); - DrmRights rights = manager.queryRights(content); - InputStream stream = content.getContentInputStream(rights); - - Uri contentUri = null; - if (mimeType.startsWith("audio/")) { - contentUri = DrmStore.Audio.CONTENT_URI; - } else if (mimeType.startsWith("image/")) { - contentUri = DrmStore.Images.CONTENT_URI; - } else { - Log.w(TAG, "unsupported mime type " + mimeType); - } - - if (contentUri != null) { - ContentValues values = new ContentValues(3); - values.put(DrmStore.Columns.TITLE, title); - values.put(DrmStore.Columns.SIZE, size); - values.put(DrmStore.Columns.MIME_TYPE, mimeType); - - Uri uri = cr.insert(contentUri, values); - if (uri != null) { - os = cr.openOutputStream(uri); - - byte[] buffer = new byte[1000]; - int count; - - while ((count = stream.read(buffer)) != -1) { - os.write(buffer, 0, count); - } - result = new Intent(); - result.setDataAndType(uri, mimeType); - - } - } - } catch (Exception e) { - Log.e(TAG, "pushing file failed", e); - } finally { - try { - if (fis != null) - fis.close(); - if (os != null) - os.close(); - } catch (IOException e) { - Log.e(TAG, "IOException in DrmStore.addDrmFile()", e); - } - } - - return result; - } - - /** - * Utility function to enforce any permissions required to access DRM - * content. - * - * @param context A context used for checking calling permission. - */ - public static void enforceAccessDrmPermission(Context context) { - if (context.checkCallingOrSelfPermission(ACCESS_DRM_PERMISSION) != - PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires DRM permission"); - } - } - -} diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index cb6300f691f4fe4ab3aa31908b4c4bdf3e72451e..f69cad096d38ef55a746ba85ef127021831cf27b 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -19,8 +19,8 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; @@ -118,6 +118,7 @@ public final class MediaStore { * sense for apps that can support large-scale search of music, such as services connected * to an online database of music which can be streamed and played on the device. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = "android.media.action.MEDIA_PLAY_FROM_SEARCH"; @@ -134,6 +135,7 @@ public final class MediaStore { * sense for apps that can support large-scale search of text media, such as services connected * to an online database of books and/or magazines which can be read on the device. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH = "android.media.action.TEXT_OPEN_FROM_SEARCH"; @@ -150,6 +152,7 @@ public final class MediaStore { * sense for apps that can support large-scale search of video, such as services connected to an * online database of videos which can be streamed and played on the device. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH = "android.media.action.VIDEO_PLAY_FROM_SEARCH"; @@ -202,6 +205,7 @@ public final class MediaStore { /** * The name of the Intent action used to launch a camera in still image mode. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA"; /** @@ -216,12 +220,14 @@ public final class MediaStore { * this flag is used, so launching more than one activity is strongly * discouraged. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = "android.media.action.STILL_IMAGE_CAMERA_SECURE"; /** * The name of the Intent action used to launch a camera in video mode. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA"; /** @@ -235,6 +241,7 @@ public final class MediaStore { * value of EXTRA_OUTPUT. * @see #EXTRA_OUTPUT */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; /** @@ -256,6 +263,7 @@ public final class MediaStore { * @see #ACTION_IMAGE_CAPTURE * @see #EXTRA_OUTPUT */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE"; @@ -274,6 +282,7 @@ public final class MediaStore { * @see #EXTRA_SIZE_LIMIT * @see #EXTRA_DURATION_LIMIT */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE"; /** @@ -532,7 +541,8 @@ public final class MediaStore { private static final Object sThumbBufLock = new Object(); private static byte[] sThumbBuf; - private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { + private static Bitmap getMiniThumbFromFile( + Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { Bitmap bitmap = null; Uri thumbUri = null; try { @@ -577,6 +587,7 @@ public final class MediaStore { if (c != null) c.close(); } } + /** * This method ensure thumbnails associated with origId are generated and decode the byte * stream from database (MICRO_KIND) or file (MINI_KIND). diff --git a/core/java/android/provider/OpenableColumns.java b/core/java/android/provider/OpenableColumns.java index f548baea725615f71023207e3c48a944eb2f2f49..faf96b7f5cb5259c86516eb6d6e6fad6f78589a1 100644 --- a/core/java/android/provider/OpenableColumns.java +++ b/core/java/android/provider/OpenableColumns.java @@ -16,11 +16,17 @@ package android.provider; +import android.content.ContentResolver; +import android.content.Intent; + /** - * These are standard columns for openable URIs. (See - * {@link android.content.Intent#CATEGORY_OPENABLE}.) If possible providers that have openable URIs - * should support these columns. To find the content type of a URI use - * {@link android.content.ContentResolver#getType(android.net.Uri)} as normal. + * These are standard columns for openable URIs. Providers that serve openable + * URIs must support at least these columns when queried. + *

    + * To find the content type of a URI, use + * {@link ContentResolver#getType(android.net.Uri)}. + * + * @see Intent#CATEGORY_OPENABLE */ public interface OpenableColumns { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 81e76ab3dea21f872accb54d6bc27186356fd85c..7f245394d246223cc943ceef7e542dee3190b90a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -33,6 +33,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.database.SQLException; +import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.Uri; import android.net.wifi.WifiManager; @@ -158,6 +159,38 @@ public final class Settings { public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS"; + /** + * Activity Action: Show trusted credentials settings, opening to the user tab, + * to allow management of installed credentials. + *

    + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_TRUSTED_CREDENTIALS_USER = + "com.android.settings.TRUSTED_CREDENTIALS_USER"; + + /** + * Activity Action: Show dialog explaining that an installed CA cert may enable + * monitoring of encrypted network traffic. + *

    + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MONITORING_CERT_INFO = + "com.android.settings.MONITORING_CERT_INFO"; + /** * Activity Action: Show settings to allow configuration of privacy options. *

    @@ -342,8 +375,9 @@ public final class Settings { /** * Activity Action: Show settings to manage the user input dictionary. *

    - * In some cases, a matching Activity may not exist, so ensure you - * safeguard against this. + * Starting with {@link android.os.Build.VERSION_CODES#KITKAT}, + * it is guaranteed there will always be an appropriate implementation for this Intent action. + * In prior releases of the platform this was optional, so ensure you safeguard against it. *

    * Input: Nothing. *

    @@ -641,6 +675,23 @@ public final class Settings { public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; + /** + * Activity Action: Show NFC Tap & Pay settings + *

    + * This shows UI that allows the user to configure Tap&Pay + * settings. + *

    + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

    + * Input: Nothing. + *

    + * Output: Nothing + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NFC_PAYMENT_SETTINGS = + "android.settings.NFC_PAYMENT_SETTINGS"; + /** * Activity Action: Show Daydream settings. *

    @@ -671,6 +722,33 @@ public final class Settings { public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.NOTIFICATION_LISTENER_SETTINGS"; + /** + * Activity Action: Show settings for video captioning. + *

    + * In some cases, a matching Activity may not exist, so ensure you safeguard + * against this. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS"; + + /** + * Activity Action: Show the top level print settings. + *

    + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PRINT_SETTINGS = + "android.settings.ACTION_PRINT_SETTINGS"; + // End of Intent actions for Settings /** @@ -736,6 +814,10 @@ public final class Settings { private static final String TAG = "Settings"; private static final boolean LOCAL_LOGV = false; + // Lock ensures that when enabling/disabling the master location switch, we don't end up + // with a partial enable/disable state in multi-threaded situations. + private static final Object mLocationSettingsLock = new Object(); + public static class SettingNotFoundException extends AndroidException { public SettingNotFoundException(String msg) { super(msg); @@ -1011,6 +1093,14 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.WAIT_FOR_DEBUGGER); MOVED_TO_GLOBAL.add(Settings.Global.SHOW_PROCESSES); MOVED_TO_GLOBAL.add(Settings.Global.ALWAYS_FINISH_ACTIVITIES); + MOVED_TO_GLOBAL.add(Settings.Global.TZINFO_UPDATE_CONTENT_URL); + MOVED_TO_GLOBAL.add(Settings.Global.TZINFO_UPDATE_METADATA_URL); + MOVED_TO_GLOBAL.add(Settings.Global.SELINUX_UPDATE_CONTENT_URL); + MOVED_TO_GLOBAL.add(Settings.Global.SELINUX_UPDATE_METADATA_URL); + MOVED_TO_GLOBAL.add(Settings.Global.SMS_SHORT_CODES_UPDATE_CONTENT_URL); + MOVED_TO_GLOBAL.add(Settings.Global.SMS_SHORT_CODES_UPDATE_METADATA_URL); + MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_CONTENT_URL); + MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_METADATA_URL); } /** @hide */ @@ -2316,6 +2406,15 @@ public final class Settings { */ public static final String POINTER_SPEED = "pointer_speed"; + /** + * I am the lolrus. + *

    + * Nonzero values indicate that the user has a bukkit. + * Backward-compatible with PrefGetPreference(prefAllowEasterEggs). + * @hide + */ + public static final String EGG_MODE = "egg_mode"; + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. @@ -2342,9 +2441,7 @@ public final class Settings { SCREEN_BRIGHTNESS_MODE, SCREEN_AUTO_BRIGHTNESS_ADJ, VIBRATE_INPUT_DEVICES, - MODE_RINGER, // moved to global MODE_RINGER_STREAMS_AFFECTED, - MUTE_STREAMS_AFFECTED, VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, @@ -2381,7 +2478,9 @@ public final class Settings { SIP_CALL_OPTIONS, SIP_RECEIVE_CALLS, POINTER_SPEED, - VIBRATE_WHEN_RINGING + VIBRATE_WHEN_RINGING, + RINGTONE, + NOTIFICATION_SOUND }; // Settings moved to Settings.Secure @@ -2864,6 +2963,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; @@ -2898,6 +3002,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); @@ -2926,6 +3035,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); } @@ -3183,6 +3297,13 @@ public final class Settings { public static final String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility"; + /** + * bluetooth HCI snoop log configuration + * @hide + */ + public static final String BLUETOOTH_HCI_LOG = + "bluetooth_hci_log"; + /** * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead */ @@ -3227,9 +3348,42 @@ public final class Settings { /** * Comma-separated list of location providers that activities may access. + * + * @deprecated use {@link #LOCATION_MODE} */ + @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; + /** + * The degree of location access enabled by the user. + *

    + * 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; + /** + * Network Location Provider disabled, but GPS and other sensors enabled. + */ + public static final int LOCATION_MODE_SENSORS_ONLY = 1; + /** + * Reduced power usage, such as limiting the number of GPS updates per hour. Requests + * with {@link android.location.Criteria#POWER_HIGH} may be downgraded to + * {@link android.location.Criteria#POWER_MEDIUM}. + */ + public static final int LOCATION_MODE_BATTERY_SAVING = 2; + /** + * Best-effort location computation allowed. + */ + public static final int LOCATION_MODE_HIGH_ACCURACY = 3; + /** * A flag containing settings used for biometric weak * @hide @@ -3493,12 +3647,139 @@ public final class Settings { public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE = "accessibility_display_magnification_auto_update"; + /** + * Setting that specifies whether timed text (captions) should be + * displayed in video content. Text display properties are controlled by + * the following settings: + *

      + *
    • {@link #ACCESSIBILITY_CAPTIONING_LOCALE} + *
    • {@link #ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR} + *
    • {@link #ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR} + *
    • {@link #ACCESSIBILITY_CAPTIONING_EDGE_COLOR} + *
    • {@link #ACCESSIBILITY_CAPTIONING_EDGE_TYPE} + *
    • {@link #ACCESSIBILITY_CAPTIONING_TYPEFACE} + *
    • {@link #ACCESSIBILITY_CAPTIONING_FONT_SCALE} + *
    + * + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_ENABLED = + "accessibility_captioning_enabled"; + + /** + * Setting that specifies the language for captions as a locale string, + * e.g. en_US. + * + * @see java.util.Locale#toString + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_LOCALE = + "accessibility_captioning_locale"; + + /** + * Integer property that specifies the preset style for captions, one + * of: + *
      + *
    • {@link android.view.accessibility.CaptioningManager.CaptionStyle#PRESET_CUSTOM} + *
    • a valid index of {@link android.view.accessibility.CaptioningManager.CaptionStyle#PRESETS} + *
    + * + * @see java.util.Locale#toString + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_PRESET = + "accessibility_captioning_preset"; + + /** + * Integer property that specifes the background color for captions as a + * packed 32-bit color. + * + * @see android.graphics.Color#argb + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR = + "accessibility_captioning_background_color"; + + /** + * Integer property that specifes the foreground color for captions as a + * packed 32-bit color. + * + * @see android.graphics.Color#argb + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR = + "accessibility_captioning_foreground_color"; + + /** + * Integer property that specifes the edge type for captions, one of: + *
      + *
    • {@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_NONE} + *
    • {@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_OUTLINE} + *
    • {@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_DROP_SHADOW} + *
    + * + * @see #ACCESSIBILITY_CAPTIONING_EDGE_COLOR + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_EDGE_TYPE = + "accessibility_captioning_edge_type"; + + /** + * Integer property that specifes the edge color for captions as a + * packed 32-bit color. + * + * @see #ACCESSIBILITY_CAPTIONING_EDGE_TYPE + * @see android.graphics.Color#argb + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_EDGE_COLOR = + "accessibility_captioning_edge_color"; + + /** + * String property that specifies the typeface for captions, one of: + *
      + *
    • DEFAULT + *
    • MONOSPACE + *
    • SANS_SERIF + *
    • SERIF + *
    + * + * @see android.graphics.Typeface + * @hide + */ + public static final String ACCESSIBILITY_CAPTIONING_TYPEFACE = + "accessibility_captioning_typeface"; + + /** + * Floating point property that specifies font scaling for captions. + * + * @hide + */ + 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. * @hide */ public static final String LONG_PRESS_TIMEOUT = "long_press_timeout"; + /** + * 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. @@ -3968,6 +4249,14 @@ public final class Settings { */ public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service"; + /** + * Stores whether an user has consented to have apps verified through PAM. + * The value is boolean (1 or 0). + * + * @hide + */ + public static final String PACKAGE_VERIFIER_USER_CONSENT = + "package_verifier_user_consent"; /** * The {@link ComponentName} string of the selected spell checker service which is @@ -4066,6 +4355,18 @@ public final class Settings { */ public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component"; + /** + * The default NFC payment component + * @hide + */ + public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; + + /** + * Specifies the package name currently configured to be the primary sms application + * @hide + */ + public static final String SMS_DEFAULT_APPLICATION = "sms_default_application"; + /** * Name of a package that the current user has explicitly allowed to see all of that * user's notifications. @@ -4074,12 +4375,25 @@ public final class Settings { */ public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners"; + /** @hide */ + public static final String BAR_SERVICE_COMPONENT = "bar_service_component"; + + /** @hide */ + public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations"; + + /** + * This is the query URI for finding a print service to install. + * + * @hide + */ + public static final String PRINT_SERVICE_SEARCH_URI = "print_service_search_uri"; + /** - * Whether or not to enable the dial pad autocomplete functionality. + * This is the query URI for finding a NFC payment service to install. * * @hide */ - public static final String DIALPAD_AUTOCOMPLETE = "dialpad_autocomplete"; + public static final String PAYMENT_SERVICE_SEARCH_URI = "payment_service_search_uri"; /** * This are the settings to be backed up. @@ -4106,6 +4420,14 @@ public final class Settings { TOUCH_EXPLORATION_ENABLED, ACCESSIBILITY_ENABLED, ACCESSIBILITY_SPEAK_PASSWORD, + ACCESSIBILITY_CAPTIONING_ENABLED, + ACCESSIBILITY_CAPTIONING_LOCALE, + ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, + ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, + ACCESSIBILITY_CAPTIONING_EDGE_TYPE, + ACCESSIBILITY_CAPTIONING_EDGE_COLOR, + ACCESSIBILITY_CAPTIONING_TYPEFACE, + ACCESSIBILITY_CAPTIONING_FONT_SCALE, TTS_USE_DEFAULTS, TTS_DEFAULT_RATE, TTS_DEFAULT_PITCH, @@ -4121,16 +4443,20 @@ public final class Settings { MOUNT_UMS_AUTOSTART, MOUNT_UMS_PROMPT, MOUNT_UMS_NOTIFY_ENABLED, - UI_NIGHT_MODE, - DIALPAD_AUTOCOMPLETE + UI_NIGHT_MODE }; /** * Helper method for determining if a location provider is enabled. + * * @param cr the content resolver to use * @param provider the location provider to query * @return true if the provider is enabled + * + * @deprecated use {@link #LOCATION_MODE} or + * {@link LocationManager#isProviderEnabled(String)} */ + @Deprecated public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { return isLocationProviderEnabledForUser(cr, provider, UserHandle.myUserId()); } @@ -4141,8 +4467,11 @@ 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 #LOCATION_MODE} or + * {@link LocationManager#isProviderEnabled(String)} * @hide */ + @Deprecated public static final boolean isLocationProviderEnabledForUser(ContentResolver cr, String provider, int userId) { String allowedProviders = Settings.Secure.getStringForUser(cr, LOCATION_PROVIDERS_ALLOWED, userId); @@ -4154,7 +4483,9 @@ 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 #putInt(ContentResolver, String, int)} and {@link #LOCATION_MODE} */ + @Deprecated public static final void setLocationProviderEnabled(ContentResolver cr, String provider, boolean enabled) { setLocationProviderEnabledForUser(cr, provider, enabled, UserHandle.myUserId()); @@ -4162,24 +4493,99 @@ 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 + * @return true if the value was set, false on database errors + * @deprecated use {@link #putIntForUser(ContentResolver, String, int, int)} and + * {@link #LOCATION_MODE} * @hide */ - public static final void setLocationProviderEnabledForUser(ContentResolver cr, + @Deprecated + public static final boolean setLocationProviderEnabledForUser(ContentResolver cr, String provider, boolean enabled, int userId) { - // to ensure thread safety, we write the provider name with a '+' or '-' - // and let the SettingsProvider handle it rather than reading and modifying - // the list of enabled providers. - if (enabled) { - provider = "+" + provider; - } else { - provider = "-" + provider; + synchronized (mLocationSettingsLock) { + // to ensure thread safety, we write the provider name with a '+' or '-' + // and let the SettingsProvider handle it rather than reading and modifying + // the list of enabled providers. + if (enabled) { + provider = "+" + provider; + } else { + provider = "-" + provider; + } + return putStringForUser(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider, + userId); + } + } + + /** + * 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} + * @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 + */ + private static final boolean setLocationModeForUser(ContentResolver cr, int mode, + int userId) { + synchronized (mLocationSettingsLock) { + boolean gps = false; + boolean network = false; + switch (mode) { + case LOCATION_MODE_OFF: + break; + case LOCATION_MODE_SENSORS_ONLY: + gps = true; + break; + case LOCATION_MODE_BATTERY_SAVING: + network = true; + break; + case LOCATION_MODE_HIGH_ACCURACY: + gps = true; + network = true; + break; + default: + throw new IllegalArgumentException("Invalid location mode: " + mode); + } + boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser( + cr, LocationManager.GPS_PROVIDER, gps, userId); + boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser( + cr, LocationManager.NETWORK_PROVIDER, network, userId); + return gpsSuccess && nlpSuccess; + } + } + + /** + * 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 + * @param userId the userId for which to read the mode + * @return the location mode + */ + private static final int getLocationModeForUser(ContentResolver cr, int userId) { + synchronized (mLocationSettingsLock) { + boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser( + cr, LocationManager.GPS_PROVIDER, userId); + boolean networkEnabled = Settings.Secure.isLocationProviderEnabledForUser( + cr, LocationManager.NETWORK_PROVIDER, userId); + if (gpsEnabled && networkEnabled) { + return LOCATION_MODE_HIGH_ACCURACY; + } else if (gpsEnabled) { + return LOCATION_MODE_SENSORS_ONLY; + } else if (networkEnabled) { + return LOCATION_MODE_BATTERY_SAVING; + } else { + return LOCATION_MODE_OFF; + } } - putStringForUser(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider, - userId); } } @@ -4775,6 +5181,29 @@ public final class Settings { */ public static final String WIFI_DISPLAY_ON = "wifi_display_on"; + /** + * Whether Wifi display certification mode is enabled/disabled + * 0=disabled. 1=enabled. + * @hide + */ + public static final String WIFI_DISPLAY_CERTIFICATION_ON = + "wifi_display_certification_on"; + + /** + * WPS Configuration method used by Wifi display, this setting only + * takes effect when WIFI_DISPLAY_CERTIFICATION_ON is 1 (enabled). + * + * Possible values are: + * + * WpsInfo.INVALID: use default WPS method chosen by framework + * WpsInfo.PBC : use Push button + * WpsInfo.KEYPAD : use Keypad + * WpsInfo.DISPLAY: use Display + * @hide + */ + public static final String WIFI_DISPLAY_WPS_CONFIG = + "wifi_display_wps_config"; + /** * Whether to notify the user of open networks. *

    @@ -4936,6 +5365,13 @@ public final class Settings { public static final String DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS = "data_stall_alarm_aggressive_delay_in_ms"; + /** + * The number of milliseconds to allow the provisioning apn to remain active + * @hide + */ + public static final String PROVISIONING_APN_ALARM_DELAY_IN_MS = + "provisioning_apn_alarm_delay_in_ms"; + /** * The interval in milliseconds at which to check gprs registration * after the first registration mismatch of gprs and voice service, @@ -5124,6 +5560,25 @@ public final class Settings { */ public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay"; + + /** + * Network sampling interval, in seconds. We'll generate link information + * about bytes/packets sent and error rates based on data sampled in this interval + * + * @hide + */ + + public static final String CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS = + "connectivity_sampling_interval_in_seconds"; + + /** + * The series of successively longer delays used in retrying to download PAC file. + * Last delay is used between successful PAC downloads. + * + * @hide + */ + public static final String PAC_CHANGE_DELAY = "pac_change_delay"; + /** * Setting to turn off captive portal detection. Feature is enabled by * default and the setting needs to be set to 0 to disable it. @@ -5218,6 +5673,13 @@ public final class Settings { public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST = "global_http_proxy_exclusion_list"; + /** + * The location PAC File for the proxy. + * @hide + */ + public static final String + GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url"; + /** * Enables the UI setting to allow the user to specify the global HTTP * proxy and associated exclusion list. @@ -5242,6 +5704,9 @@ public final class Settings { /** {@hide} */ public static final String BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_"; + /** {@hide} */ + public static final String + BLUETOOTH_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_"; /** * Get the key that retrieves a bluetooth headset's priority. @@ -5267,6 +5732,13 @@ public final class Settings { return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); } + /** + * Get the key that retrieves a bluetooth map priority. + * @hide + */ + public static final String getBluetoothMapPriorityKey(String address) { + return BLUETOOTH_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); + } /** * Scaling factor for normal window animations. Setting to 0 will * disable window animations. @@ -5441,6 +5913,22 @@ public final class Settings { */ public static final String SELINUX_STATUS = "selinux_status"; + /** + * Developer setting to force RTL layout. + * @hide + */ + public static final String DEVELOPMENT_FORCE_RTL = "debug.force_rtl"; + + /** + * Milliseconds after screen-off after which low battery sounds will be silenced. + * + * If zero, battery sounds will always play. + * Defaults to @integer/def_low_battery_sound_timeout in SettingsProvider. + * + * @hide + */ + public static final String LOW_BATTERY_SOUND_TIMEOUT = "low_battery_sound_timeout"; + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. @@ -5460,7 +5948,6 @@ public final class Settings { public static final String[] SETTINGS_TO_BACKUP = { BUGREPORT_IN_POWER_MENU, STAY_ON_WHILE_PLUGGED_IN, - MODE_RINGER, AUTO_TIME, AUTO_TIME_ZONE, POWER_SOUNDS_ENABLED, diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index bfea9cadef89205345711fe76f0f9ea400ca0dad..2e0e59bc91c96bcbde90a2d5d77c2ed8932eb860 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -164,11 +164,19 @@ public abstract class NotificationListenerService extends Service { private class INotificationListenerWrapper extends INotificationListener.Stub { @Override public void onNotificationPosted(StatusBarNotification sbn) { - NotificationListenerService.this.onNotificationPosted(sbn); + try { + NotificationListenerService.this.onNotificationPosted(sbn); + } catch (Throwable t) { + Log.w(TAG, "Error running onNotificationPosted", t); + } } @Override public void onNotificationRemoved(StatusBarNotification sbn) { - NotificationListenerService.this.onNotificationRemoved(sbn); + try { + NotificationListenerService.this.onNotificationRemoved(sbn); + } catch (Throwable t) { + Log.w(TAG, "Error running onNotificationRemoved", t); + } } } } diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 19f8678a427a15cfde177667846c8248536ef638..b5b9e142989101797bd4fea4f9978bc300f7e6dd 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -33,23 +33,12 @@ public class StatusBarNotification implements Parcelable { private final int uid; private final String basePkg; private final int initialPid; - // TODO: make this field private and move callers to an accessor that - // ensures sourceUser is applied. - private final Notification notification; private final UserHandle user; private final long postTime; private final int score; - /** This is temporarily needed for the JB MR1 PDK. - * @hide */ - @Deprecated - public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score, - Notification notification) { - this(pkg, id, tag, uid, initialPid, score, notification, UserHandle.OWNER); - } - /** @hide */ public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score, Notification notification, UserHandle user) { diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java index 457e66c6dbddbe318d14a0891b5b585568c8b688..e991d84807c186b260fec03c917c2b4340266be3 100644 --- a/core/java/android/speech/RecognizerIntent.java +++ b/core/java/android/speech/RecognizerIntent.java @@ -55,7 +55,10 @@ public class RecognizerIntent { *

    Starting this intent with just {@link Activity#startActivity(Intent)} is not supported. * You must either use {@link Activity#startActivityForResult(Intent, int)}, or provide a * PendingIntent, to receive recognition results. - * + * + *

    The implementation of this API is likely to stream audio to remote servers to perform + * speech recognition which can use a substantial amount of bandwidth. + * *

    Required extras: *

      *
    • {@link #EXTRA_LANGUAGE_MODEL} diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 8fee41d1bcb7f83104ec0579a562694433882149..94aedbdf1d6a343f41b09175620feafc35bee539 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -39,8 +39,14 @@ import java.util.Queue; * This class provides access to the speech recognition service. This service allows access to the * speech recognizer. Do not instantiate this class directly, instead, call * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be - * invoked only from the main application thread. Please note that the application must have - * {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class. + * invoked only from the main application thread. + * + *

      The implementation of this API is likely to stream audio to remote servers to perform speech + * recognition. As such this API is not intended to be used for continuous recognition, which would + * consume a significant amount of battery and bandwidth. + * + *

      Please note that the application must have {@link android.Manifest.permission#RECORD_AUDIO} + * permission to use this class. */ public class SpeechRecognizer { /** DEBUG value to enable verbose debug prints */ diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java index 6398d3d27e2633e5f6c0c1a66e9446a75aace285..12a026b0ba06d2ee734f8dcf5471f5e9627a28c7 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; *

    * * 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 { @@ -41,6 +41,7 @@ public final class SynthesisRequest { private String mVariant; private int mSpeechRate; private int mPitch; + private int mCallerUid; public SynthesisRequest(String text, Bundle params) { mText = text; @@ -97,6 +98,13 @@ public final class SynthesisRequest { return mParams; } + /** + * Gets the request caller Uid. + */ + public int getCallerUid() { + return mCallerUid; + } + /** * Sets the locale for the request. */ @@ -119,4 +127,11 @@ public final class SynthesisRequest { void setPitch(int pitch) { mPitch = pitch; } + + /** + * Sets Caller Uid + */ + void setCallerUid(int uid) { + mCallerUid = uid; + } } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 578a86eb521d9b8a167efdadff567c6560228d9e..b8083632bc4fcba716c9ec88b721180fce86ce35 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -561,7 +561,8 @@ public class TextToSpeech { * The context this instance is running in. * @param listener * The {@link TextToSpeech.OnInitListener} that will be called when the - * TextToSpeech engine has initialized. + * TextToSpeech engine has initialized. In a case of a failure the listener + * may be called immediately, before TextToSpeech instance is fully constructed. */ public TextToSpeech(Context context, OnInitListener listener) { this(context, listener, null); @@ -575,7 +576,8 @@ public class TextToSpeech { * The context this instance is running in. * @param listener * The {@link TextToSpeech.OnInitListener} that will be called when the - * TextToSpeech engine has initialized. + * TextToSpeech engine has initialized. In a case of a failure the listener + * may be called immediately, before TextToSpeech instance is fully constructed. * @param engine Package name of the TTS engine to use. */ public TextToSpeech(Context context, OnInitListener listener, String engine) { diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 703dcff1688cb8733ad5e257edbadde43b37bb52..575855c1f1937d40ebe39cfc8bb195e9dea90ba5 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -557,11 +557,13 @@ public abstract class TextToSpeechService extends Service { // guarded by 'this'. private AbstractSynthesisCallback mSynthesisCallback; private final EventLogger mEventLogger; + private final int mCallerUid; public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid, Bundle params, String text) { super(callerIdentity, callerUid, callerPid, params); mText = text; + mCallerUid = callerUid; mSynthesisRequest = new SynthesisRequest(mText, mParams); mDefaultLocale = getSettingsLocale(); setRequestParams(mSynthesisRequest); @@ -611,7 +613,7 @@ public abstract class TextToSpeechService extends Service { private void setRequestParams(SynthesisRequest request) { request.setLanguage(getLanguage(), getCountry(), getVariant()); request.setSpeechRate(getSpeechRate()); - + request.setCallerUid(mCallerUid); request.setPitch(getPitch()); } diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java index 2706bc72d7e835e44cae6e2bc8f3a0563e41688b..5fbd22e273e8446bea3847dfe1a8aac96d8bdf39 100644 --- a/core/java/android/speech/tts/TtsEngines.java +++ b/core/java/android/speech/tts/TtsEngines.java @@ -355,7 +355,18 @@ public class TtsEngines { return v1Locale; } - private String getDefaultLocale() { + /** + * Return the default device locale in form of 3 letter codes delimited by + * {@link #LOCALE_DELIMITER}: + *
      + *
    • "ISO 639-2/T language code" if locale have no country entry
    • + *
    • "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code " + * if locale have no variant entry
    • + *
    • "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code + * {@link #LOCALE_DELIMITER} variant" if locale have variant entry
    • + *
    + */ + public String getDefaultLocale() { final Locale locale = Locale.getDefault(); // Note that the default locale might have an empty variant diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java index eacd40d0db29e353eb9252bf629361c2a05c5cd6..b1c07f592c793d5fbbf3d68ea490425d63081199 100644 --- a/core/java/android/text/AndroidBidi.java +++ b/core/java/android/text/AndroidBidi.java @@ -60,6 +60,9 @@ import android.text.Layout.Directions; */ public static Directions directions(int dir, byte[] levels, int lstart, char[] chars, int cstart, int len) { + if (len == 0) { + return Layout.DIRS_ALL_LEFT_TO_RIGHT; + } int baseLevel = dir == Layout.DIR_LEFT_TO_RIGHT ? 0 : 1; int curLevel = levels[lstart]; diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 122f8a171d0d8c0ff527ce269892144975af44ea..06935ae7a2c9922974ee8dd78788fa17fbb6bdad 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -502,17 +502,19 @@ public class DynamicLayout extends Layout } mNumberOfBlocks = newNumberOfBlocks; + int newFirstChangedBlock; final int deltaLines = newLineCount - (endLine - startLine + 1); if (deltaLines != 0) { // Display list whose index is >= mIndexFirstChangedBlock is valid // but it needs to update its drawing location. - mIndexFirstChangedBlock = firstBlock + numAddedBlocks; - for (int i = mIndexFirstChangedBlock; i < mNumberOfBlocks; i++) { + newFirstChangedBlock = firstBlock + numAddedBlocks; + for (int i = newFirstChangedBlock; i < mNumberOfBlocks; i++) { mBlockEndLines[i] += deltaLines; } } else { - mIndexFirstChangedBlock = mNumberOfBlocks; + newFirstChangedBlock = mNumberOfBlocks; } + mIndexFirstChangedBlock = Math.min(mIndexFirstChangedBlock, newFirstChangedBlock); int blockIndex = firstBlock; if (createBlockBefore) { diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index a6e8c7032eaf40e97df76ecb36eeb93ea13596c2..9dfd3837d0f80ab47d1b483f1c37e4964f240555 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1115,7 +1115,7 @@ public abstract class Layout { float dist = Math.abs(getPrimaryHorizontal(max) - horiz); - if (dist < bestdist) { + if (dist <= bestdist) { bestdist = dist; best = max; } diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 8929930431125559338c2b80eb65f41885740b68..34274a62ce66c325bd920c52fdda73e9057e8725 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -1288,6 +1288,55 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return mFilters; } + // Same as SpannableStringInternal + @Override + public boolean equals(Object o) { + if (o instanceof Spanned && + toString().equals(o.toString())) { + Spanned other = (Spanned) o; + // Check span data + Object[] otherSpans = other.getSpans(0, other.length(), Object.class); + if (mSpanCount == otherSpans.length) { + for (int i = 0; i < mSpanCount; ++i) { + Object thisSpan = mSpans[i]; + Object otherSpan = otherSpans[i]; + if (thisSpan == this) { + if (other != otherSpan || + getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || + getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || + getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { + return false; + } + } else if (!thisSpan.equals(otherSpan) || + getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || + getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || + getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { + return false; + } + } + return true; + } + } + return false; + } + + // Same as SpannableStringInternal + @Override + public int hashCode() { + int hash = toString().hashCode(); + hash = hash * 31 + mSpanCount; + for (int i = 0; i < mSpanCount; ++i) { + Object span = mSpans[i]; + if (span != this) { + hash = hash * 31 + span.hashCode(); + } + hash = hash * 31 + getSpanStart(span); + hash = hash * 31 + getSpanEnd(span); + hash = hash * 31 + getSpanFlags(span); + } + return hash; + } + private static final InputFilter[] NO_FILTERS = new InputFilter[0]; private InputFilter[] mFilters = NO_FILTERS; diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java index 0825bf3b5665c3033943b6c6884d8d0e66c66d94..456a3e5a6b1803cd68171bfaa2f61756f381fbfe 100644 --- a/core/java/android/text/SpannableStringInternal.java +++ b/core/java/android/text/SpannableStringInternal.java @@ -358,6 +358,55 @@ import java.lang.reflect.Array; } } + // Same as SpannableStringBuilder + @Override + public boolean equals(Object o) { + if (o instanceof Spanned && + toString().equals(o.toString())) { + Spanned other = (Spanned) o; + // Check span data + Object[] otherSpans = other.getSpans(0, other.length(), Object.class); + if (mSpanCount == otherSpans.length) { + for (int i = 0; i < mSpanCount; ++i) { + Object thisSpan = mSpans[i]; + Object otherSpan = otherSpans[i]; + if (thisSpan == this) { + if (other != otherSpan || + getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || + getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || + getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { + return false; + } + } else if (!thisSpan.equals(otherSpan) || + getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || + getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || + getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { + return false; + } + } + return true; + } + } + return false; + } + + // Same as SpannableStringBuilder + @Override + public int hashCode() { + int hash = toString().hashCode(); + hash = hash * 31 + mSpanCount; + for (int i = 0; i < mSpanCount; ++i) { + Object span = mSpans[i]; + if (span != this) { + hash = hash * 31 + span.hashCode(); + } + hash = hash * 31 + getSpanStart(span); + hash = hash * 31 + getSpanEnd(span); + hash = hash * 31 + getSpanFlags(span); + } + return hash; + } + private String mText; private Object[] mSpans; private int[] mSpanData; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 129127920904ea5dc8fac2b1a59b8b301b0d9937..e7d6fda8e8f03314fb414024c2ec74e472713c2f 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -474,6 +474,8 @@ public class StaticLayout extends Layout { mLineCount < mMaximumVisibleLineCount) { // Log.e("text", "output last " + bufEnd); + measured.setPara(source, bufStart, bufEnd, textDir); + paint.getFontMetricsInt(fm); v = out(source, @@ -482,7 +484,7 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, null, null, fm, false, - needMultiply, null, DEFAULT_DIR, true, bufEnd, + needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, includepad, trackpad, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index e2035c2afe1fd9f4c1ec1e5ee353e66e4ff150b3..596ca8c958ec276167895843d004db32323d8b07 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -19,6 +19,8 @@ package android.text; import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemProperties; +import android.provider.Settings; import android.text.style.AbsoluteSizeSpan; import android.text.style.AlignmentSpan; import android.text.style.BackgroundColorSpan; @@ -1743,8 +1745,10 @@ public class TextUtils { return View.LAYOUT_DIRECTION_RTL; } } - - return View.LAYOUT_DIRECTION_LTR; + // If forcing into RTL layout mode, return RTL as default, else LTR + return SystemProperties.getBoolean(Settings.Global.DEVELOPMENT_FORCE_RTL, false) + ? View.LAYOUT_DIRECTION_RTL + : View.LAYOUT_DIRECTION_LTR; } /** diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 30bb447ff4b6af182814e67b5d21bf749790b426..ba6f1d4ba07a1462547da2ca17573730b4d16883 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -56,7 +56,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0 && MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0) { + MetaKeyKeyListener.META_SELECTING, event) != 0) { return widget.showContextMenu(); } } diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java index 4fede32f5e069adad3a87904917b5ff45120c7d3..63607fa34872e7285787c84b73e0045985cbaa26 100644 --- a/core/java/android/text/method/BaseKeyListener.java +++ b/core/java/android/text/method/BaseKeyListener.java @@ -75,7 +75,7 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener } // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible. - if (event.isAltPressed() || getMetaState(content, META_ALT_ON) == 1) { + if (getMetaState(content, META_ALT_ON, event) == 1) { if (deleteLine(view, content)) { return true; } diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java index 113a4becfde6ed3f9e0efd53772c342206678659..155a2c4fcfa096148fb2a7b7e01db36427ea659b 100644 --- a/core/java/android/text/method/BaseMovementMethod.java +++ b/core/java/android/text/method/BaseMovementMethod.java @@ -135,7 +135,7 @@ public class BaseMovementMethod implements MovementMethod { */ protected int getMovementMetaState(Spannable buffer, KeyEvent event) { // We ignore locked modifiers and SHIFT. - int metaState = (event.getMetaState() | MetaKeyKeyListener.getMetaState(buffer)) + int metaState = MetaKeyKeyListener.getMetaState(buffer, event) & ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED); return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK; } diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java index ce51fae06ecf5ec39df6e230dc2ba8f5906c5d96..bb8b0de8a9f362dd3c7235db0c3d9674d4f8d0cc 100644 --- a/core/java/android/text/method/DialerKeyListener.java +++ b/core/java/android/text/method/DialerKeyListener.java @@ -53,7 +53,7 @@ public class DialerKeyListener extends NumberKeyListener * from the KeyEvent. */ protected int lookup(KeyEvent event, Spannable content) { - int meta = event.getMetaState() | getMetaState(content); + int meta = getMetaState(content, event); int number = event.getNumber(); /* diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java index aff233df16a39609bcdcaac1c44edef0f5d5f01a..3855ff3ae44be3d138045866fc781ecd51b5025e 100644 --- a/core/java/android/text/method/LinkMovementMethod.java +++ b/core/java/android/text/method/LinkMovementMethod.java @@ -35,6 +35,11 @@ public class LinkMovementMethod extends ScrollingMovementMethod { private static final int UP = 2; private static final int DOWN = 3; + @Override + public boolean canSelectArbitrarily() { + return true; + } + @Override protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode, int movementMetaState, KeyEvent event) { diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java index 0a097f99bd0a635a60dd35f207a47bfa8e1bd69c..e9db5fde77ed19b17f6aad2164d7591e66d7741f 100644 --- a/core/java/android/text/method/MetaKeyKeyListener.java +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -135,6 +135,9 @@ public abstract class MetaKeyKeyListener { private static final Object SYM = new NoCopySpan.Concrete(); private static final Object SELECTING = new NoCopySpan.Concrete(); + private static final int PRESSED_RETURN_VALUE = 1; + private static final int LOCKED_RETURN_VALUE = 2; + /** * Resets all meta state to inactive. */ @@ -160,10 +163,35 @@ public abstract class MetaKeyKeyListener { getActive(text, SELECTING, META_SELECTING, META_SELECTING); } + /** + * Gets the state of the meta keys for a specific key event. + * + * For input devices that use toggled key modifiers, the `toggled' state + * is stored into the text buffer. This method retrieves the meta state + * for this event, accounting for the stored state. If the event has been + * created by a device that does not support toggled key modifiers, like + * a virtual device for example, the stored state is ignored. + * + * @param text the buffer in which the meta key would have been pressed. + * @param event the event for which to evaluate the meta state. + * @return an integer in which each bit set to one represents a pressed + * or locked meta key. + */ + public static final int getMetaState(final CharSequence text, final KeyEvent event) { + int metaState = event.getMetaState(); + if (event.getKeyCharacterMap().getModifierBehavior() + == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) { + metaState |= getMetaState(text); + } + return metaState; + } + + // As META_SELECTING is @hide we should not mention it in public comments, hence the + // omission in @param meta /** * Gets the state of a particular meta key. * - * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING + * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON * @param text the buffer in which the meta key would have been pressed. * * @return 0 if inactive, 1 if active, 2 if locked. @@ -171,22 +199,53 @@ public abstract class MetaKeyKeyListener { public static final int getMetaState(CharSequence text, int meta) { switch (meta) { case META_SHIFT_ON: - return getActive(text, CAP, 1, 2); + return getActive(text, CAP, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); case META_ALT_ON: - return getActive(text, ALT, 1, 2); + return getActive(text, ALT, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); case META_SYM_ON: - return getActive(text, SYM, 1, 2); + return getActive(text, SYM, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); case META_SELECTING: - return getActive(text, SELECTING, 1, 2); + return getActive(text, SELECTING, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE); default: return 0; } } + /** + * Gets the state of a particular meta key to use with a particular key event. + * + * If the key event has been created by a device that does not support toggled + * key modifiers, like a virtual keyboard for example, only the meta state in + * the key event is considered. + * + * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON + * @param text the buffer in which the meta key would have been pressed. + * @param event the event for which to evaluate the meta state. + * @return 0 if inactive, 1 if active, 2 if locked. + */ + public static final int getMetaState(final CharSequence text, final int meta, + final KeyEvent event) { + int metaState = event.getMetaState(); + if (event.getKeyCharacterMap().getModifierBehavior() + == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) { + metaState |= getMetaState(text); + } + if (META_SELECTING == meta) { + // #getMetaState(long, int) does not support META_SELECTING, but we want the same + // behavior as #getMetaState(CharSequence, int) so we need to do it here + if ((metaState & META_SELECTING) != 0) { + // META_SELECTING is only ever set to PRESSED and can't be LOCKED, so return 1 + return 1; + } + return 0; + } + return getMetaState(metaState, meta); + } + private static int getActive(CharSequence text, Object meta, int on, int lock) { if (!(text instanceof Spanned)) { @@ -430,18 +489,18 @@ public abstract class MetaKeyKeyListener { public static final int getMetaState(long state, int meta) { switch (meta) { case META_SHIFT_ON: - if ((state & META_CAP_LOCKED) != 0) return 2; - if ((state & META_SHIFT_ON) != 0) return 1; + if ((state & META_CAP_LOCKED) != 0) return LOCKED_RETURN_VALUE; + if ((state & META_SHIFT_ON) != 0) return PRESSED_RETURN_VALUE; return 0; case META_ALT_ON: - if ((state & META_ALT_LOCKED) != 0) return 2; - if ((state & META_ALT_ON) != 0) return 1; + if ((state & META_ALT_LOCKED) != 0) return LOCKED_RETURN_VALUE; + if ((state & META_ALT_ON) != 0) return PRESSED_RETURN_VALUE; return 0; case META_SYM_ON: - if ((state & META_SYM_LOCKED) != 0) return 2; - if ((state & META_SYM_ON) != 0) return 1; + if ((state & META_SYM_LOCKED) != 0) return LOCKED_RETURN_VALUE; + if ((state & META_SYM_ON) != 0) return PRESSED_RETURN_VALUE; return 0; default: @@ -599,4 +658,3 @@ public abstract class MetaKeyKeyListener { private static final int LOCKED = Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT); } - diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java index 5d4c7329ab3bac78337d96f4f422e268734b9e50..988d566f5a7eecde2e178d8574f6cb2d49816d5f 100644 --- a/core/java/android/text/method/NumberKeyListener.java +++ b/core/java/android/text/method/NumberKeyListener.java @@ -41,7 +41,7 @@ public abstract class NumberKeyListener extends BaseKeyListener protected abstract char[] getAcceptedChars(); protected int lookup(KeyEvent event, Spannable content) { - return event.getMatch(getAcceptedChars(), event.getMetaState() | getMetaState(content)); + return event.getMatch(getAcceptedChars(), getMetaState(content, event)); } public CharSequence filter(CharSequence source, int start, int end, diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java index 98316ae58da60e2da789347aa38b7e26d411c4f4..0bd46bc992a0b8bd57701b27f5ed0cb6ad77de1a 100644 --- a/core/java/android/text/method/QwertyKeyListener.java +++ b/core/java/android/text/method/QwertyKeyListener.java @@ -108,7 +108,7 @@ public class QwertyKeyListener extends BaseKeyListener { // QWERTY keyboard normal case - int i = event.getUnicodeChar(event.getMetaState() | getMetaState(content)); + int i = event.getUnicodeChar(getMetaState(content, event)); if (!mFullKeyboard) { int count = event.getRepeatCount(); diff --git a/core/java/android/transition/AutoTransition.java b/core/java/android/transition/AutoTransition.java new file mode 100644 index 0000000000000000000000000000000000000000..6e46021e5b3c0ce0b7deb34b9fea080edd6989a7 --- /dev/null +++ b/core/java/android/transition/AutoTransition.java @@ -0,0 +1,41 @@ +/* + * 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.transition; + +/** + * Utility class for creating a default transition that automatically fades, + * moves, and resizes views during a scene change. + * + *

    An AutoTransition can be described in a resource file by using the + * tag autoTransition, along with the other standard + * attributes of {@link android.R.styleable#Transition}.

    + */ +public class AutoTransition extends TransitionSet { + + /** + * Constructs an AutoTransition object, which is a TransitionSet which + * first fades out disappearing targets, then moves and resizes existing + * targets, and finally fades in appearing targets. + * + */ + public AutoTransition() { + setOrdering(ORDERING_SEQUENTIAL); + addTransition(new Fade(Fade.OUT)). + addTransition(new ChangeBounds()). + addTransition(new Fade(Fade.IN)); + } +} diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java new file mode 100644 index 0000000000000000000000000000000000000000..8053bffa808c6a05a86acf973657fbb53be6b384 --- /dev/null +++ b/core/java/android/transition/ChangeBounds.java @@ -0,0 +1,310 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.RectEvaluator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.view.View; +import android.view.ViewGroup; + +import java.util.Map; + +/** + * This transition captures the layout bounds of target views before and after + * the scene change and animates those changes during the transition. + * + *

    A ChangeBounds transition can be described in a resource file by using the + * tag changeBounds, along with the other standard + * attributes of {@link android.R.styleable#Transition}.

    + */ +public class ChangeBounds extends Transition { + + private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds"; + private static final String PROPNAME_PARENT = "android:changeBounds:parent"; + private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX"; + private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY"; + private static final String[] sTransitionProperties = { + PROPNAME_BOUNDS, + PROPNAME_PARENT, + PROPNAME_WINDOW_X, + PROPNAME_WINDOW_Y + }; + + int[] tempLocation = new int[2]; + boolean mResizeClip = false; + boolean mReparent = false; + private static final String LOG_TAG = "ChangeBounds"; + + private static RectEvaluator sRectEvaluator = new RectEvaluator(); + + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + public void setResizeClip(boolean resizeClip) { + mResizeClip = resizeClip; + } + + /** + * Setting this flag tells ChangeBounds to track the before/after parent + * of every view using this transition. The flag is not enabled by + * default because it requires the parent instances to be the same + * in the two scenes or else all parents must use ids to allow + * the transition to determine which parents are the same. + * + * @param reparent true if the transition should track the parent + * container of target views and animate parent changes. + */ + public void setReparent(boolean reparent) { + mReparent = reparent; + } + + private void captureValues(TransitionValues values) { + View view = values.view; + values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(), + view.getRight(), view.getBottom())); + values.values.put(PROPNAME_PARENT, values.view.getParent()); + values.view.getLocationInWindow(tempLocation); + values.values.put(PROPNAME_WINDOW_X, tempLocation[0]); + values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } + Map startParentVals = startValues.values; + Map endParentVals = endValues.values; + ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT); + ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT); + if (startParent == null || endParent == null) { + return null; + } + final View view = endValues.view; + boolean parentsEqual = (startParent == endParent) || + (startParent.getId() == endParent.getId()); + // TODO: Might want reparenting to be separate/subclass transition, or at least + // triggered by a property on ChangeBounds. Otherwise, we're forcing the requirement that + // all parents in layouts have IDs to avoid layout-inflation resulting in a side-effect + // of reparenting the views. + if (!mReparent || parentsEqual) { + Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS); + Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + int startLeft = startBounds.left; + int endLeft = endBounds.left; + int startTop = startBounds.top; + int endTop = endBounds.top; + int startRight = startBounds.right; + int endRight = endBounds.right; + int startBottom = startBounds.bottom; + int endBottom = endBounds.bottom; + int startWidth = startRight - startLeft; + int startHeight = startBottom - startTop; + int endWidth = endRight - endLeft; + int endHeight = endBottom - endTop; + int numChanges = 0; + if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) { + if (startLeft != endLeft) ++numChanges; + if (startTop != endTop) ++numChanges; + if (startRight != endRight) ++numChanges; + if (startBottom != endBottom) ++numChanges; + } + if (numChanges > 0) { + if (!mResizeClip) { + PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges]; + int pvhIndex = 0; + if (startLeft != endLeft) view.setLeft(startLeft); + if (startTop != endTop) view.setTop(startTop); + if (startRight != endRight) view.setRight(startRight); + if (startBottom != endBottom) view.setBottom(startBottom); + if (startLeft != endLeft) { + pvh[pvhIndex++] = PropertyValuesHolder.ofInt("left", startLeft, endLeft); + } + if (startTop != endTop) { + pvh[pvhIndex++] = PropertyValuesHolder.ofInt("top", startTop, endTop); + } + if (startRight != endRight) { + pvh[pvhIndex++] = PropertyValuesHolder.ofInt("right", + startRight, endRight); + } + if (startBottom != endBottom) { + pvh[pvhIndex++] = PropertyValuesHolder.ofInt("bottom", + startBottom, endBottom); + } + ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh); + if (view.getParent() instanceof ViewGroup) { + final ViewGroup parent = (ViewGroup) view.getParent(); + parent.suppressLayout(true); + TransitionListener transitionListener = new TransitionListenerAdapter() { + boolean mCanceled = false; + + @Override + public void onTransitionCancel(Transition transition) { + parent.suppressLayout(false); + mCanceled = true; + } + + @Override + public void onTransitionEnd(Transition transition) { + if (!mCanceled) { + parent.suppressLayout(false); + } + } + + @Override + public void onTransitionPause(Transition transition) { + parent.suppressLayout(false); + } + + @Override + public void onTransitionResume(Transition transition) { + parent.suppressLayout(true); + } + }; + addListener(transitionListener); + } + return anim; + } else { + if (startWidth != endWidth) view.setRight(endLeft + + Math.max(startWidth, endWidth)); + if (startHeight != endHeight) view.setBottom(endTop + + Math.max(startHeight, endHeight)); + // TODO: don't clobber TX/TY + if (startLeft != endLeft) view.setTranslationX(startLeft - endLeft); + if (startTop != endTop) view.setTranslationY(startTop - endTop); + // Animate location with translationX/Y and size with clip bounds + float transXDelta = endLeft - startLeft; + float transYDelta = endTop - startTop; + int widthDelta = endWidth - startWidth; + int heightDelta = endHeight - startHeight; + numChanges = 0; + if (transXDelta != 0) numChanges++; + if (transYDelta != 0) numChanges++; + if (widthDelta != 0 || heightDelta != 0) numChanges++; + PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges]; + int pvhIndex = 0; + if (transXDelta != 0) { + pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationX", + view.getTranslationX(), 0); + } + if (transYDelta != 0) { + pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationY", + view.getTranslationY(), 0); + } + if (widthDelta != 0 || heightDelta != 0) { + Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight); + Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight); + pvh[pvhIndex++] = PropertyValuesHolder.ofObject("clipBounds", + sRectEvaluator, tempStartBounds, tempEndBounds); + } + ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh); + if (view.getParent() instanceof ViewGroup) { + final ViewGroup parent = (ViewGroup) view.getParent(); + parent.suppressLayout(true); + TransitionListener transitionListener = new TransitionListenerAdapter() { + boolean mCanceled = false; + + @Override + public void onTransitionCancel(Transition transition) { + parent.suppressLayout(false); + mCanceled = true; + } + + @Override + public void onTransitionEnd(Transition transition) { + if (!mCanceled) { + parent.suppressLayout(false); + } + } + + @Override + public void onTransitionPause(Transition transition) { + parent.suppressLayout(false); + } + + @Override + public void onTransitionResume(Transition transition) { + parent.suppressLayout(true); + } + }; + addListener(transitionListener); + } + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setClipBounds(null); + } + }); + return anim; + } + } + } else { + int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X); + int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y); + int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X); + int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y); + // TODO: also handle size changes: check bounds and animate size changes + if (startX != endX || startY != endY) { + sceneRoot.getLocationInWindow(tempLocation); + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + final BitmapDrawable drawable = new BitmapDrawable(bitmap); + view.setVisibility(View.INVISIBLE); + sceneRoot.getOverlay().add(drawable); + Rect startBounds1 = new Rect(startX - tempLocation[0], startY - tempLocation[1], + startX - tempLocation[0] + view.getWidth(), + startY - tempLocation[1] + view.getHeight()); + Rect endBounds1 = new Rect(endX - tempLocation[0], endY - tempLocation[1], + endX - tempLocation[0] + view.getWidth(), + endY - tempLocation[1] + view.getHeight()); + ObjectAnimator anim = ObjectAnimator.ofObject(drawable, "bounds", + sRectEvaluator, startBounds1, endBounds1); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + sceneRoot.getOverlay().remove(drawable); + view.setVisibility(View.VISIBLE); + } + }); + return anim; + } + } + return null; + } +} diff --git a/core/java/android/transition/ChangeText.java b/core/java/android/transition/ChangeText.java new file mode 100644 index 0000000000000000000000000000000000000000..8677a564c01e1654f7eda754f8d360538756d4cf --- /dev/null +++ b/core/java/android/transition/ChangeText.java @@ -0,0 +1,311 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.graphics.Color; +import android.util.Log; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.Map; + +/** + * This transition tracks changes to the text in TextView targets. If the text + * changes between the start and end scenes, the transition ensures that the + * starting text stays until the transition ends, at which point it changes + * to the end text. This is useful in situations where you want to resize a + * text view to its new size before displaying the text that goes there. + * + * @hide + */ +public class ChangeText extends Transition { + + private static final String LOG_TAG = "TextChange"; + + private static final String PROPNAME_TEXT = "android:textchange:text"; + private static final String PROPNAME_TEXT_SELECTION_START = + "android:textchange:textSelectionStart"; + private static final String PROPNAME_TEXT_SELECTION_END = + "android:textchange:textSelectionEnd"; + private static final String PROPNAME_TEXT_COLOR = "android:textchange:textColor"; + + private int mChangeBehavior = CHANGE_BEHAVIOR_KEEP; + + /** + * Flag specifying that the text in affected/changing TextView targets will keep + * their original text during the transition, setting it to the final text when + * the transition ends. This is the default behavior. + * + * @see #setChangeBehavior(int) + */ + public static final int CHANGE_BEHAVIOR_KEEP = 0; + /** + * Flag specifying that the text changing animation should first fade + * out the original text completely. The new text is set on the target + * view at the end of the fade-out animation. This transition is typically + * used with a later {@link #CHANGE_BEHAVIOR_IN} transition, allowing more + * flexibility than the {@link #CHANGE_BEHAVIOR_OUT_IN} by allowing other + * transitions to be run sequentially or in parallel with these fades. + * + * @see #setChangeBehavior(int) + */ + public static final int CHANGE_BEHAVIOR_OUT = 1; + /** + * Flag specifying that the text changing animation should fade in the + * end text into the affected target view(s). This transition is typically + * used in conjunction with an earlier {@link #CHANGE_BEHAVIOR_OUT} + * transition, possibly with other transitions running as well, such as + * a sequence to fade out, then resize the view, then fade in. + * + * @see #setChangeBehavior(int) + */ + public static final int CHANGE_BEHAVIOR_IN = 2; + /** + * Flag specifying that the text changing animation should first fade + * out the original text completely and then fade in the + * new text. + * + * @see #setChangeBehavior(int) + */ + public static final int CHANGE_BEHAVIOR_OUT_IN = 3; + + private static final String[] sTransitionProperties = { + PROPNAME_TEXT, + PROPNAME_TEXT_SELECTION_START, + PROPNAME_TEXT_SELECTION_END + }; + + /** + * Sets the type of changing animation that will be run, one of + * {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, + * {@link #CHANGE_BEHAVIOR_IN}, and {@link #CHANGE_BEHAVIOR_OUT_IN}. + * + * @param changeBehavior The type of fading animation to use when this + * transition is run. + * @return this textChange object. + */ + public ChangeText setChangeBehavior(int changeBehavior) { + if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) { + mChangeBehavior = changeBehavior; + } + return this; + } + + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + /** + * Returns the type of changing animation that will be run. + * + * @return either {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, + * {@link #CHANGE_BEHAVIOR_IN}, or {@link #CHANGE_BEHAVIOR_OUT_IN}. + */ + public int getChangeBehavior() { + return mChangeBehavior; + } + + private void captureValues(TransitionValues transitionValues) { + if (transitionValues.view instanceof TextView) { + TextView textview = (TextView) transitionValues.view; + transitionValues.values.put(PROPNAME_TEXT, textview.getText()); + if (textview instanceof EditText) { + transitionValues.values.put(PROPNAME_TEXT_SELECTION_START, + textview.getSelectionStart()); + transitionValues.values.put(PROPNAME_TEXT_SELECTION_END, + textview.getSelectionEnd()); + } + if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { + transitionValues.values.put(PROPNAME_TEXT_COLOR, textview.getCurrentTextColor()); + } + } + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null || + !(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) { + return null; + } + final TextView view = (TextView) endValues.view; + Map startVals = startValues.values; + Map endVals = endValues.values; + final CharSequence startText = startVals.get(PROPNAME_TEXT) != null ? + (CharSequence) startVals.get(PROPNAME_TEXT) : ""; + final CharSequence endText = endVals.get(PROPNAME_TEXT) != null ? + (CharSequence) endVals.get(PROPNAME_TEXT) : ""; + final int startSelectionStart, startSelectionEnd, endSelectionStart, endSelectionEnd; + if (view instanceof EditText) { + startSelectionStart = startVals.get(PROPNAME_TEXT_SELECTION_START) != null ? + (Integer) startVals.get(PROPNAME_TEXT_SELECTION_START) : -1; + startSelectionEnd = startVals.get(PROPNAME_TEXT_SELECTION_END) != null ? + (Integer) startVals.get(PROPNAME_TEXT_SELECTION_END) : startSelectionStart; + endSelectionStart = endVals.get(PROPNAME_TEXT_SELECTION_START) != null ? + (Integer) endVals.get(PROPNAME_TEXT_SELECTION_START) : -1; + endSelectionEnd = endVals.get(PROPNAME_TEXT_SELECTION_END) != null ? + (Integer) endVals.get(PROPNAME_TEXT_SELECTION_END) : endSelectionStart; + } else { + startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1; + } + if (!startText.equals(endText)) { + final int startColor; + final int endColor; + if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { + view.setText(startText); + if (view instanceof EditText) { + setSelection(((EditText) view), startSelectionStart, startSelectionEnd); + } + } + Animator anim; + if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) { + startColor = endColor = 0; + anim = ValueAnimator.ofFloat(0, 1); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (startText.equals(view.getText())) { + // Only set if it hasn't been changed since anim started + view.setText(endText); + if (view instanceof EditText) { + setSelection(((EditText) view), endSelectionStart, endSelectionEnd); + } + } + } + }); + } else { + startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR); + endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR); + // Fade out start text + ValueAnimator outAnim = null, inAnim = null; + if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || + mChangeBehavior == CHANGE_BEHAVIOR_OUT) { + outAnim = ValueAnimator.ofInt(255, 0); + outAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int currAlpha = (Integer) animation.getAnimatedValue(); + view.setTextColor(currAlpha << 24 | startColor & 0xff0000 | + startColor & 0xff00 | startColor & 0xff); + } + }); + outAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (startText.equals(view.getText())) { + // Only set if it hasn't been changed since anim started + view.setText(endText); + if (view instanceof EditText) { + setSelection(((EditText) view), endSelectionStart, + endSelectionEnd); + } + } + // restore opaque alpha and correct end color + view.setTextColor(endColor); + } + }); + } + if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || + mChangeBehavior == CHANGE_BEHAVIOR_IN) { + inAnim = ValueAnimator.ofInt(0, 255); + inAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int currAlpha = (Integer) animation.getAnimatedValue(); + view.setTextColor(currAlpha << 24 | Color.red(endColor) << 16 | + Color.green(endColor) << 8 | Color.red(endColor)); + } + }); + inAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + // restore opaque alpha and correct end color + view.setTextColor(endColor); + } + }); + } + if (outAnim != null && inAnim != null) { + anim = new AnimatorSet(); + ((AnimatorSet) anim).playSequentially(outAnim, inAnim); + } else if (outAnim != null) { + anim = outAnim; + } else { + // Must be an in-only animation + anim = inAnim; + } + } + TransitionListener transitionListener = new TransitionListenerAdapter() { + int mPausedColor = 0; + + @Override + public void onTransitionPause(Transition transition) { + if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { + view.setText(endText); + if (view instanceof EditText) { + setSelection(((EditText) view), endSelectionStart, endSelectionEnd); + } + } + if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { + mPausedColor = view.getCurrentTextColor(); + view.setTextColor(endColor); + } + } + + @Override + public void onTransitionResume(Transition transition) { + if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { + view.setText(startText); + if (view instanceof EditText) { + setSelection(((EditText) view), startSelectionStart, startSelectionEnd); + } + } + if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { + view.setTextColor(mPausedColor); + } + } + }; + addListener(transitionListener); + if (DBG) { + Log.d(LOG_TAG, "createAnimator returning " + anim); + } + return anim; + } + return null; + } + + private void setSelection(EditText editText, int start, int end) { + if (start >= 0 && end >= 0) { + editText.setSelection(start, end); + } + } +} diff --git a/core/java/android/transition/Crossfade.java b/core/java/android/transition/Crossfade.java new file mode 100644 index 0000000000000000000000000000000000000000..69ce872a24fbc9c5e6eb685259fa86ddd0907f61 --- /dev/null +++ b/core/java/android/transition/Crossfade.java @@ -0,0 +1,296 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.RectEvaluator; +import android.animation.ValueAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.util.Log; +import android.view.SurfaceView; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOverlay; + +import java.util.Map; + +/** + * This transition captures bitmap representations of target views before and + * after the scene change and fades between them. + * + *

    Note: This transition is not compatible with {@link TextureView} + * or {@link SurfaceView}.

    + * + * @hide + */ +public class Crossfade extends Transition { + // TODO: Add a hook that lets a Transition call user code to query whether it should run on + // a given target view. This would save bitmap comparisons in this transition, for example. + + private static final String LOG_TAG = "Crossfade"; + + private static final String PROPNAME_BITMAP = "android:crossfade:bitmap"; + private static final String PROPNAME_DRAWABLE = "android:crossfade:drawable"; + private static final String PROPNAME_BOUNDS = "android:crossfade:bounds"; + + private static RectEvaluator sRectEvaluator = new RectEvaluator(); + + private int mFadeBehavior = FADE_BEHAVIOR_REVEAL; + private int mResizeBehavior = RESIZE_BEHAVIOR_SCALE; + + /** + * Flag specifying that the fading animation should cross-fade + * between the old and new representation of all affected target + * views. This means that the old representation will fade out + * while the new one fades in. This effect may work well on views + * without solid backgrounds, such as TextViews. + * + * @see #setFadeBehavior(int) + */ + public static final int FADE_BEHAVIOR_CROSSFADE = 0; + /** + * Flag specifying that the fading animation should reveal the + * new representation of all affected target views. This means + * that the old representation will fade out, gradually + * revealing the new representation, which remains opaque + * the whole time. This effect may work well on views + * with solid backgrounds, such as ImageViews. + * + * @see #setFadeBehavior(int) + */ + public static final int FADE_BEHAVIOR_REVEAL = 1; + /** + * Flag specifying that the fading animation should first fade + * out the original representation completely and then fade in the + * new one. This effect may be more suitable than the other + * fade behaviors for views with. + * + * @see #setFadeBehavior(int) + */ + public static final int FADE_BEHAVIOR_OUT_IN = 2; + + /** + * Flag specifying that the transition should not animate any + * changes in size between the old and new target views. + * This means that no scaling will take place as a result of + * this transition + * + * @see #setResizeBehavior(int) + */ + public static final int RESIZE_BEHAVIOR_NONE = 0; + /** + * Flag specifying that the transition should animate any + * changes in size between the old and new target views. + * This means that the animation will scale the start/end + * representations of affected views from the starting size + * to the ending size over the course of the animation. + * This effect may work well on images, but is not recommended + * for text. + * + * @see #setResizeBehavior(int) + */ + public static final int RESIZE_BEHAVIOR_SCALE = 1; + + // TODO: Add fade/resize behaviors to xml resources + + /** + * Sets the type of fading animation that will be run, one of + * {@link #FADE_BEHAVIOR_CROSSFADE} and {@link #FADE_BEHAVIOR_REVEAL}. + * + * @param fadeBehavior The type of fading animation to use when this + * transition is run. + */ + public Crossfade setFadeBehavior(int fadeBehavior) { + if (fadeBehavior >= FADE_BEHAVIOR_CROSSFADE && fadeBehavior <= FADE_BEHAVIOR_OUT_IN) { + mFadeBehavior = fadeBehavior; + } + return this; + } + + /** + * Returns the fading behavior of the animation. + * + * @return This crossfade object. + * @see #setFadeBehavior(int) + */ + public int getFadeBehavior() { + return mFadeBehavior; + } + + /** + * Sets the type of resizing behavior that will be used during the + * transition animation, one of {@link #RESIZE_BEHAVIOR_NONE} and + * {@link #RESIZE_BEHAVIOR_SCALE}. + * + * @param resizeBehavior The type of resizing behavior to use when this + * transition is run. + */ + public Crossfade setResizeBehavior(int resizeBehavior) { + if (resizeBehavior >= RESIZE_BEHAVIOR_NONE && resizeBehavior <= RESIZE_BEHAVIOR_SCALE) { + mResizeBehavior = resizeBehavior; + } + return this; + } + + /** + * Returns the resizing behavior of the animation. + * + * @return This crossfade object. + * @see #setResizeBehavior(int) + */ + public int getResizeBehavior() { + return mResizeBehavior; + } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } + final boolean useParentOverlay = mFadeBehavior != FADE_BEHAVIOR_REVEAL; + final View view = endValues.view; + Map startVals = startValues.values; + Map endVals = endValues.values; + Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS); + Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS); + Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP); + Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP); + final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE); + final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE); + if (Transition.DBG) { + Log.d(LOG_TAG, "StartBitmap.sameAs(endBitmap) = " + startBitmap.sameAs(endBitmap) + + " for start, end: " + startBitmap + ", " + endBitmap); + } + if (startDrawable != null && endDrawable != null && !startBitmap.sameAs(endBitmap)) { + ViewOverlay overlay = useParentOverlay ? + ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); + if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { + overlay.add(endDrawable); + } + overlay.add(startDrawable); + // The transition works by placing the end drawable under the start drawable and + // gradually fading out the start drawable. So it's not really a cross-fade, but rather + // a reveal of the end scene over time. Also, animate the bounds of both drawables + // to mimic the change in the size of the view itself between scenes. + ObjectAnimator anim; + if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { + // Fade out completely halfway through the transition + anim = ObjectAnimator.ofInt(startDrawable, "alpha", 255, 0, 0); + } else { + anim = ObjectAnimator.ofInt(startDrawable, "alpha", 0); + } + anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + // TODO: some way to auto-invalidate views based on drawable changes? callbacks? + view.invalidate(startDrawable.getBounds()); + } + }); + ObjectAnimator anim1 = null; + if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { + // start fading in halfway through the transition + anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 0, 1); + } else if (mFadeBehavior == FADE_BEHAVIOR_CROSSFADE) { + anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1); + } + if (Transition.DBG) { + Log.d(LOG_TAG, "Crossfade: created anim " + anim + " for start, end values " + + startValues + ", " + endValues); + } + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ViewOverlay overlay = useParentOverlay ? + ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); + overlay.remove(startDrawable); + if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { + overlay.remove(endDrawable); + } + } + }); + AnimatorSet set = new AnimatorSet(); + set.playTogether(anim); + if (anim1 != null) { + set.playTogether(anim1); + } + if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE && !startBounds.equals(endBounds)) { + if (Transition.DBG) { + Log.d(LOG_TAG, "animating from startBounds to endBounds: " + + startBounds + ", " + endBounds); + } + Animator anim2 = ObjectAnimator.ofObject(startDrawable, "bounds", + sRectEvaluator, startBounds, endBounds); + set.playTogether(anim2); + if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE) { + // TODO: How to handle resizing with a CROSSFADE (vs. REVEAL) effect + // when we are animating the view directly? + Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds", + sRectEvaluator, startBounds, endBounds); + set.playTogether(anim3); + } + } + return set; + } else { + return null; + } + } + + private void captureValues(TransitionValues transitionValues) { + View view = transitionValues.view; + Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + if (mFadeBehavior != FADE_BEHAVIOR_REVEAL) { + bounds.offset(view.getLeft(), view.getTop()); + } + transitionValues.values.put(PROPNAME_BOUNDS, bounds); + + if (Transition.DBG) { + Log.d(LOG_TAG, "Captured bounds " + transitionValues.values.get(PROPNAME_BOUNDS)); + } + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), + Bitmap.Config.ARGB_8888); + if (view instanceof TextureView) { + bitmap = ((TextureView) view).getBitmap(); + } else { + Canvas c = new Canvas(bitmap); + view.draw(c); + } + transitionValues.values.put(PROPNAME_BITMAP, bitmap); + // TODO: I don't have resources, can't call the non-deprecated method? + BitmapDrawable drawable = new BitmapDrawable(bitmap); + // TODO: lrtb will be wrong if the view has transXY set + drawable.setBounds(bounds); + transitionValues.values.put(PROPNAME_DRAWABLE, drawable); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } +} diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java new file mode 100644 index 0000000000000000000000000000000000000000..8edb1ff7440572d79d9ea237aed76e7cb3bb75cc --- /dev/null +++ b/core/java/android/transition/Fade.java @@ -0,0 +1,342 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +/** + * This transition tracks changes to the visibility of target views in the + * start and end scenes and fades views in or out when they become visible + * or non-visible. Visibility is determined by both the + * {@link View#setVisibility(int)} state of the view as well as whether it + * is parented in the current view hierarchy. + * + *

    The ability of this transition to fade out a particular view, and the + * way that that fading operation takes place, is based on + * the situation of the view in the view hierarchy. For example, if a view was + * simply removed from its parent, then the view will be added into a {@link + * android.view.ViewGroupOverlay} while fading. If a visible view is + * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the + * visibility will be changed to {@link View#VISIBLE} for the duration of + * the animation. However, if a view is in a hierarchy which is also altering + * its visibility, the situation can be more complicated. In general, if a + * view that is no longer in the hierarchy in the end scene still has a + * parent (so its parent hierarchy was removed, but it was not removed from + * its parent), then it will be left alone to avoid side-effects from + * improperly removing it from its parent. The only exception to this is if + * the previous {@link Scene} was + * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context) + * created from a layout resource file}, then it is considered safe to un-parent + * the starting scene view in order to fade it out.

    + * + *

    A Fade transition can be described in a resource file by using the + * tag fade, along with the standard + * attributes of {@link android.R.styleable#Fade} and + * {@link android.R.styleable#Transition}.

    + + */ +public class Fade extends Visibility { + + private static boolean DBG = Transition.DBG && false; + + private static final String LOG_TAG = "Fade"; + private static final String PROPNAME_SCREEN_X = "android:fade:screenX"; + private static final String PROPNAME_SCREEN_Y = "android:fade:screenY"; + + /** + * Fading mode used in {@link #Fade(int)} to make the transition + * operate on targets that are appearing. Maybe be combined with + * {@link #OUT} to fade both in and out. + */ + public static final int IN = 0x1; + /** + * Fading mode used in {@link #Fade(int)} to make the transition + * operate on targets that are disappearing. Maybe be combined with + * {@link #IN} to fade both in and out. + */ + public static final int OUT = 0x2; + + private int mFadingMode; + + /** + * Constructs a Fade transition that will fade targets in and out. + */ + public Fade() { + this(IN | OUT); + } + + /** + * Constructs a Fade transition that will fade targets in + * and/or out, according to the value of fadingMode. + * + * @param fadingMode The behavior of this transition, a combination of + * {@link #IN} and {@link #OUT}. + */ + public Fade(int fadingMode) { + mFadingMode = fadingMode; + } + + /** + * Utility method to handle creating and running the Animator. + */ + private Animator createAnimation(View view, float startAlpha, float endAlpha, + AnimatorListenerAdapter listener) { + if (startAlpha == endAlpha) { + // run listener if we're noop'ing the animation, to get the end-state results now + if (listener != null) { + listener.onAnimationEnd(null); + } + return null; + } + final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", startAlpha, + endAlpha); + if (DBG) { + Log.d(LOG_TAG, "Created animator " + anim); + } + if (listener != null) { + anim.addListener(listener); + anim.addPauseListener(listener); + } + return anim; + } + + private void captureValues(TransitionValues transitionValues) { + int[] loc = new int[2]; + transitionValues.view.getLocationOnScreen(loc); + transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]); + transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + captureValues(transitionValues); + } + + @Override + public Animator onAppear(ViewGroup sceneRoot, + TransitionValues startValues, int startVisibility, + TransitionValues endValues, int endVisibility) { + if ((mFadingMode & IN) != IN || endValues == null) { + return null; + } + final View endView = endValues.view; + if (DBG) { + View startView = (startValues != null) ? startValues.view : null; + Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " + + startView + ", " + startVisibility + ", " + endView + ", " + endVisibility); + } + endView.setTransitionAlpha(0); + TransitionListener transitionListener = new TransitionListenerAdapter() { + boolean mCanceled = false; + float mPausedAlpha; + + @Override + public void onTransitionCancel(Transition transition) { + endView.setTransitionAlpha(1); + mCanceled = true; + } + + @Override + public void onTransitionEnd(Transition transition) { + if (!mCanceled) { + endView.setTransitionAlpha(1); + } + } + + @Override + public void onTransitionPause(Transition transition) { + mPausedAlpha = endView.getTransitionAlpha(); + endView.setTransitionAlpha(1); + } + + @Override + public void onTransitionResume(Transition transition) { + endView.setTransitionAlpha(mPausedAlpha); + } + }; + addListener(transitionListener); + return createAnimation(endView, 0, 1, null); + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, + TransitionValues startValues, int startVisibility, + TransitionValues endValues, int endVisibility) { + if ((mFadingMode & OUT) != OUT) { + return null; + } + View view = null; + View startView = (startValues != null) ? startValues.view : null; + View endView = (endValues != null) ? endValues.view : null; + if (DBG) { + Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " + + startView + ", " + startVisibility + ", " + endView + ", " + endVisibility); + } + View overlayView = null; + View viewToKeep = null; + if (endView == null || endView.getParent() == null) { + if (endView != null) { + // endView was removed from its parent - add it to the overlay + view = overlayView = endView; + } else if (startView != null) { + // endView does not exist. Use startView only under certain + // conditions, because placing a view in an overlay necessitates + // it being removed from its current parent + if (startView.getParent() == null) { + // no parent - safe to use + view = overlayView = startView; + } else if (startView.getParent() instanceof View && + startView.getParent().getParent() == null) { + View startParent = (View) startView.getParent(); + int id = startParent.getId(); + if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) { + // no parent, but its parent is unparented but the parent + // hierarchy has been replaced by a new hierarchy with the same id + // and it is safe to un-parent startView + view = overlayView = startView; + } + } + } + } else { + // visibility change + if (endVisibility == View.INVISIBLE) { + view = endView; + viewToKeep = view; + } else { + // Becoming GONE + if (startView == endView) { + view = endView; + viewToKeep = view; + } else { + view = startView; + overlayView = view; + } + } + } + final int finalVisibility = endVisibility; + // TODO: add automatic facility to Visibility superclass for keeping views around + if (overlayView != null) { + // TODO: Need to do this for general case of adding to overlay + int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X); + int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y); + int[] loc = new int[2]; + sceneRoot.getLocationOnScreen(loc); + overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft()); + overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop()); + sceneRoot.getOverlay().add(overlayView); + // TODO: add automatic facility to Visibility superclass for keeping views around + final float startAlpha = 1; + float endAlpha = 0; + final View finalView = view; + final View finalOverlayView = overlayView; + final View finalViewToKeep = viewToKeep; + final ViewGroup finalSceneRoot = sceneRoot; + final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finalView.setTransitionAlpha(startAlpha); + // TODO: restore view offset from overlay repositioning + if (finalViewToKeep != null) { + finalViewToKeep.setVisibility(finalVisibility); + } + if (finalOverlayView != null) { + finalSceneRoot.getOverlay().remove(finalOverlayView); + } + } + + @Override + public void onAnimationPause(Animator animation) { + if (finalOverlayView != null) { + finalSceneRoot.getOverlay().remove(finalOverlayView); + } + } + + @Override + public void onAnimationResume(Animator animation) { + if (finalOverlayView != null) { + finalSceneRoot.getOverlay().add(finalOverlayView); + } + } + }; + return createAnimation(view, startAlpha, endAlpha, endListener); + } + if (viewToKeep != null) { + // TODO: find a different way to do this, like just changing the view to be + // VISIBLE for the duration of the transition + viewToKeep.setVisibility((View.VISIBLE)); + // TODO: add automatic facility to Visibility superclass for keeping views around + final float startAlpha = 1; + float endAlpha = 0; + final View finalView = view; + final View finalOverlayView = overlayView; + final View finalViewToKeep = viewToKeep; + final ViewGroup finalSceneRoot = sceneRoot; + final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { + boolean mCanceled = false; + float mPausedAlpha = -1; + + @Override + public void onAnimationPause(Animator animation) { + if (finalViewToKeep != null && !mCanceled) { + finalViewToKeep.setVisibility(finalVisibility); + } + mPausedAlpha = finalView.getTransitionAlpha(); + finalView.setTransitionAlpha(startAlpha); + } + + @Override + public void onAnimationResume(Animator animation) { + if (finalViewToKeep != null && !mCanceled) { + finalViewToKeep.setVisibility(View.VISIBLE); + } + finalView.setTransitionAlpha(mPausedAlpha); + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + if (mPausedAlpha >= 0) { + finalView.setTransitionAlpha(mPausedAlpha); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCanceled) { + finalView.setTransitionAlpha(startAlpha); + } + // TODO: restore view offset from overlay repositioning + if (finalViewToKeep != null && !mCanceled) { + finalViewToKeep.setVisibility(finalVisibility); + } + if (finalOverlayView != null) { + finalSceneRoot.getOverlay().remove(finalOverlayView); + } + } + }; + return createAnimation(view, startAlpha, endAlpha, endListener); + } + return null; + } + +} \ No newline at end of file diff --git a/core/java/android/transition/Recolor.java b/core/java/android/transition/Recolor.java new file mode 100644 index 0000000000000000000000000000000000000000..70111d12fdee4d00e0987a8807662dd7c6a3a0cf --- /dev/null +++ b/core/java/android/transition/Recolor.java @@ -0,0 +1,95 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * This transition tracks changes during scene changes to the + * {@link View#setBackground(android.graphics.drawable.Drawable) background} + * property of its target views (when the background is a + * {@link ColorDrawable}, as well as the + * {@link TextView#setTextColor(android.content.res.ColorStateList) + * color} of the text for target TextViews. If the color changes between + * scenes, the color change is animated. + * + * @hide + */ +public class Recolor extends Transition { + + private static final String PROPNAME_BACKGROUND = "android:recolor:background"; + private static final String PROPNAME_TEXT_COLOR = "android:recolor:textColor"; + + private void captureValues(TransitionValues transitionValues) { + transitionValues.values.put(PROPNAME_BACKGROUND, transitionValues.view.getBackground()); + if (transitionValues.view instanceof TextView) { + transitionValues.values.put(PROPNAME_TEXT_COLOR, + ((TextView)transitionValues.view).getCurrentTextColor()); + } + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } + final View view = endValues.view; + Drawable startBackground = (Drawable) startValues.values.get(PROPNAME_BACKGROUND); + Drawable endBackground = (Drawable) endValues.values.get(PROPNAME_BACKGROUND); + boolean changed = false; + if (startBackground instanceof ColorDrawable && endBackground instanceof ColorDrawable) { + ColorDrawable startColor = (ColorDrawable) startBackground; + ColorDrawable endColor = (ColorDrawable) endBackground; + if (startColor.getColor() != endColor.getColor()) { + endColor.setColor(startColor.getColor()); + changed = true; + return ObjectAnimator.ofObject(endBackground, "color", + new ArgbEvaluator(), startColor.getColor(), endColor.getColor()); + } + } + if (view instanceof TextView) { + TextView textView = (TextView) view; + int start = (Integer) startValues.values.get(PROPNAME_TEXT_COLOR); + int end = (Integer) endValues.values.get(PROPNAME_TEXT_COLOR); + if (start != end) { + textView.setTextColor(end); + changed = true; + return ObjectAnimator.ofObject(textView, "textColor", + new ArgbEvaluator(), start, end); + } + } + return null; + } +} diff --git a/core/java/android/transition/Rotate.java b/core/java/android/transition/Rotate.java new file mode 100644 index 0000000000000000000000000000000000000000..ad1720ca78b7df0d8dbe24f589ecd498353f7b04 --- /dev/null +++ b/core/java/android/transition/Rotate.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.transition; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.view.View; +import android.view.ViewGroup; + +/** + * This transition captures the rotation property of targets before and after + * the scene change and animates any changes. + * + * @hide + */ +public class Rotate extends Transition { + + private static final String PROPNAME_ROTATION = "android:rotate:rotation"; + + @Override + public void captureStartValues(TransitionValues transitionValues) { + transitionValues.values.put(PROPNAME_ROTATION, transitionValues.view.getRotation()); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + transitionValues.values.put(PROPNAME_ROTATION, transitionValues.view.getRotation()); + } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } + final View view = endValues.view; + float startRotation = (Float) startValues.values.get(PROPNAME_ROTATION); + float endRotation = (Float) endValues.values.get(PROPNAME_ROTATION); + if (startRotation != endRotation) { + view.setRotation(startRotation); + return ObjectAnimator.ofFloat(view, View.ROTATION, + startRotation, endRotation); + } + return null; + } +} diff --git a/core/java/android/transition/Scene.java b/core/java/android/transition/Scene.java new file mode 100644 index 0000000000000000000000000000000000000000..d798abec5a33b2eabd0d4cc48d0feab8054b1006 --- /dev/null +++ b/core/java/android/transition/Scene.java @@ -0,0 +1,260 @@ +/* + * 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.transition; + +import android.content.Context; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A scene represents the collection of values that various properties in the + * View hierarchy will have when the scene is applied. A Scene can be + * configured to automatically run a Transition when it is applied, which will + * animate the various property changes that take place during the + * scene change. + */ +public final class Scene { + + private Context mContext; + private int mLayoutId = -1; + private ViewGroup mSceneRoot; + private ViewGroup mLayout; // alternative to layoutId + Runnable mEnterAction, mExitAction; + private static ThreadLocal> sScenes = new ThreadLocal>(); + + /** + * Returns a Scene described by the resource file associated with the given + * layoutId parameter. If such a Scene has already been created, + * that same Scene will be returned. This caching of layoutId-based scenes enables + * sharing of common scenes between those created in code and those referenced + * by {@link TransitionManager} XML resource files. + * + * @param sceneRoot The root of the hierarchy in which scene changes + * and transitions will take place. + * @param layoutId The id of a standard layout resource file. + * @param context The context used in the process of inflating + * the layout resource. + * @return + */ + public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) { + SparseArray scenes = sScenes.get(); + if (scenes == null) { + scenes = new SparseArray(); + sScenes.set(scenes); + } + Scene scene = scenes.get(layoutId); + if (scene != null) { + return scene; + } else { + scene = new Scene(sceneRoot, layoutId, context); + scenes.put(layoutId, scene); + return scene; + } + } + + /** + * Constructs a Scene with no information about how values will change + * when this scene is applied. This constructor might be used when + * a Scene is created with the intention of being dynamically configured, + * through setting {@link #setEnterAction(Runnable)} and possibly + * {@link #setExitAction(Runnable)}. + * + * @param sceneRoot The root of the hierarchy in which scene changes + * and transitions will take place. + */ + public Scene(ViewGroup sceneRoot) { + mSceneRoot = sceneRoot; + } + + /** + * Constructs a Scene which, when entered, will remove any + * children from the sceneRoot container and will inflate and add + * the hierarchy specified by the layoutId resource file. + * + *

    This method is hidden because layoutId-based scenes should be + * created by the caching factory method {@link Scene#getCurrentScene(View)}.

    + * + * @param sceneRoot The root of the hierarchy in which scene changes + * and transitions will take place. + * @param layoutId The id of a resource file that defines the view + * hierarchy of this scene. + * @param context The context used in the process of inflating + * the layout resource. + */ + private Scene(ViewGroup sceneRoot, int layoutId, Context context) { + mContext = context; + mSceneRoot = sceneRoot; + mLayoutId = layoutId; + } + + /** + * Constructs a Scene which, when entered, will remove any + * children from the sceneRoot container and add the layout + * object as a new child of that container. + * + * @param sceneRoot The root of the hierarchy in which scene changes + * and transitions will take place. + * @param layout The view hierarchy of this scene, added as a child + * of sceneRoot when this scene is entered. + */ + public Scene(ViewGroup sceneRoot, ViewGroup layout) { + mSceneRoot = sceneRoot; + mLayout = layout; + } + + /** + * Gets the root of the scene, which is the root of the view hierarchy + * affected by changes due to this scene, and which will be animated + * when this scene is entered. + * + * @return The root of the view hierarchy affected by this scene. + */ + public ViewGroup getSceneRoot() { + return mSceneRoot; + } + + /** + * Exits this scene, if it is the current scene + * on the scene's {@link #getSceneRoot() scene root}. The current scene is + * set when {@link #enter() entering} a scene. + * Exiting a scene runs the {@link #setExitAction(Runnable) exit action} + * if there is one. + */ + public void exit() { + if (getCurrentScene(mSceneRoot) == this) { + if (mExitAction != null) { + mExitAction.run(); + } + } + } + + /** + * Enters this scene, which entails changing all values that + * are specified by this scene. These may be values associated + * with a layout view group or layout resource file which will + * now be added to the scene root, or it may be values changed by + * an {@link #setEnterAction(Runnable)} enter action}, or a + * combination of the these. No transition will be run when the + * scene is entered. To get transition behavior in scene changes, + * use one of the methods in {@link TransitionManager} instead. + */ + public void enter() { + + // Apply layout change, if any + if (mLayoutId > 0 || mLayout != null) { + // empty out parent container before adding to it + getSceneRoot().removeAllViews(); + + if (mLayoutId > 0) { + LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot); + } else { + mSceneRoot.addView(mLayout); + } + } + + // Notify next scene that it is entering. Subclasses may override to configure scene. + if (mEnterAction != null) { + mEnterAction.run(); + } + + setCurrentScene(mSceneRoot, this); + } + + /** + * Set the scene that the given view is in. The current scene is set only + * on the root view of a scene, not for every view in that hierarchy. This + * information is used by Scene to determine whether there is a previous + * scene which should be exited before the new scene is entered. + * + * @param view The view on which the current scene is being set + */ + static void setCurrentScene(View view, Scene scene) { + view.setTagInternal(com.android.internal.R.id.current_scene, scene); + } + + /** + * Gets the current {@link Scene} set on the given view. A scene is set on a view + * only if that view is the scene root. + * + * @return The current Scene set on this view. A value of null indicates that + * no Scene is currently set. + */ + static Scene getCurrentScene(View view) { + return (Scene) view.getTag(com.android.internal.R.id.current_scene); + } + + /** + * Scenes that are not defined with layout resources or + * hierarchies, or which need to perform additional steps + * after those hierarchies are changed to, should set an enter + * action, and possibly an exit action as well. An enter action + * will cause Scene to call back into application code to do + * anything else the application needs after transitions have + * captured pre-change values and after any other scene changes + * have been applied, such as the layout (if any) being added to + * the view hierarchy. After this method is called, Transitions will + * be played. + * + * @param action The runnable whose {@link Runnable#run() run()} method will + * be called when this scene is entered + * @see #setExitAction(Runnable) + * @see Scene#Scene(ViewGroup, int, Context) + * @see Scene#Scene(ViewGroup, ViewGroup) + */ + public void setEnterAction(Runnable action) { + mEnterAction = action; + } + + /** + * Scenes that are not defined with layout resources or + * hierarchies, or which need to perform additional steps + * after those hierarchies are changed to, should set an enter + * action, and possibly an exit action as well. An exit action + * will cause Scene to call back into application code to do + * anything the application needs to do after applicable transitions have + * captured pre-change values, but before any other scene changes + * have been applied, such as the new layout (if any) being added to + * the view hierarchy. After this method is called, the next scene + * will be entered, including a call to {@link #setEnterAction(Runnable)} + * if an enter action is set. + * + * @see #setEnterAction(Runnable) + * @see Scene#Scene(ViewGroup, int, Context) + * @see Scene#Scene(ViewGroup, ViewGroup) + */ + public void setExitAction(Runnable action) { + mExitAction = action; + } + + + /** + * Returns whether this Scene was created by a layout resource file, determined + * by the layoutId passed into + * {@link #getSceneForLayout(android.view.ViewGroup, int, android.content.Context)}. + * This is called by TransitionManager to determine whether it is safe for views from + * this scene to be removed from their parents when the scene is exited, which is + * used by {@link Fade} to fade these views out (the views must be removed from + * their parent in order to add them to the overlay for fading purposes). If a + * Scene is not based on a resource file, then the impact of removing views + * arbitrarily is unknown and should be avoided. + */ + boolean isCreatedFromLayoutResource() { + return (mLayoutId > 0); + } +} \ No newline at end of file diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java new file mode 100644 index 0000000000000000000000000000000000000000..b38973ce122d2b4ad3d798dc8882507ac8314ef6 --- /dev/null +++ b/core/java/android/transition/Slide.java @@ -0,0 +1,65 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +/** + * This transition captures the visibility of target objects before and + * after a scene change and animates any changes by sliding the target + * objects into or out of place. + * + * @hide + */ +public class Slide extends Visibility { + + // TODO: Add parameter for sliding factor - it's hard-coded below + + private static final TimeInterpolator sAccelerator = new AccelerateInterpolator(); + private static final TimeInterpolator sDecelerator = new DecelerateInterpolator(); + + @Override + public Animator onAppear(ViewGroup sceneRoot, + TransitionValues startValues, int startVisibility, + TransitionValues endValues, int endVisibility) { + View endView = (endValues != null) ? endValues.view : null; + endView.setTranslationY(-2 * endView.getHeight()); + ObjectAnimator anim = ObjectAnimator.ofFloat(endView, View.TRANSLATION_Y, + -2 * endView.getHeight(), 0); + anim.setInterpolator(sDecelerator); + return anim; + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, + TransitionValues startValues, int startVisibility, + TransitionValues endValues, int endVisibility) { + View startView = (startValues != null) ? startValues.view : null; + startView.setTranslationY(0); + ObjectAnimator anim = ObjectAnimator.ofFloat(startView, View.TRANSLATION_Y, 0, + -2 * startView.getHeight()); + anim.setInterpolator(sAccelerator); + return anim; + } + +} diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java new file mode 100644 index 0000000000000000000000000000000000000000..dcf668b6c443d9a3d70450b4a3eedacf7b6ab5fc --- /dev/null +++ b/core/java/android/transition/Transition.java @@ -0,0 +1,1681 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.util.ArrayMap; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.SparseArray; +import android.view.SurfaceView; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOverlay; +import android.widget.ListView; +import android.widget.Spinner; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Transition holds information about animations that will be run on its + * targets during a scene change. Subclasses of this abstract class may + * choreograph several child transitions ({@link TransitionSet} or they may + * perform custom animations themselves. Any Transition has two main jobs: + * (1) capture property values, and (2) play animations based on changes to + * captured property values. A custom transition knows what property values + * on View objects are of interest to it, and also knows how to animate + * changes to those values. For example, the {@link Fade} transition tracks + * changes to visibility-related properties and is able to construct and run + * animations that fade items in or out based on changes to those properties. + * + *

    Note: Transitions may not work correctly with either {@link SurfaceView} + * or {@link TextureView}, due to the way that these views are displayed + * on the screen. For SurfaceView, the problem is that the view is updated from + * a non-UI thread, so changes to the view due to transitions (such as moving + * and resizing the view) may be out of sync with the display inside those bounds. + * TextureView is more compatible with transitions in general, but some + * specific transitions (such as {@link Fade}) may not be compatible + * with TextureView because they rely on {@link ViewOverlay} functionality, + * which does not currently work with TextureView.

    + * + *

    Transitions can be declared in XML resource files inside the res/transition + * directory. Transition resources consist of a tag name for one of the Transition + * subclasses along with attributes to define some of the attributes of that transition. + * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:

    + * + * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds} + * + *

    Note that attributes for the transition are not required, just as they are + * optional when declared in code; Transitions created from XML resources will use + * the same defaults as their code-created equivalents. Here is a slightly more + * elaborate example which declares a {@link TransitionSet} transition with + * {@link ChangeBounds} and {@link Fade} child transitions:

    + * + * {@sample + * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet} + * + *

    In this example, the transitionOrdering attribute is used on the TransitionSet + * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior + * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade} + * transition uses a fadingMode of {@link Fade#OUT} instead of the default + * out-in behavior. Finally, note the use of the targets sub-tag, which + * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each + * of which lists a specific targetId which this transition acts upon. + * Use of targets is optional, but can be used to either limit the time spent checking + * attributes on unchanging views, or limiting the types of animations run on specific views. + * In this case, we know that only the grayscaleContainer will be + * disappearing, so we choose to limit the {@link Fade} transition to only that view.

    + * + * Further information on XML resource descriptions for transitions can be found for + * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, + * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}. + * + */ +public abstract class Transition implements Cloneable { + + private static final String LOG_TAG = "Transition"; + static final boolean DBG = false; + + private String mName = getClass().getName(); + + long mStartDelay = -1; + long mDuration = -1; + TimeInterpolator mInterpolator = null; + ArrayList mTargetIds = new ArrayList(); + ArrayList mTargets = new ArrayList(); + ArrayList mTargetIdExcludes = null; + ArrayList mTargetExcludes = null; + ArrayList mTargetTypeExcludes = null; + ArrayList mTargetIdChildExcludes = null; + ArrayList mTargetChildExcludes = null; + ArrayList mTargetTypeChildExcludes = null; + private TransitionValuesMaps mStartValues = new TransitionValuesMaps(); + private TransitionValuesMaps mEndValues = new TransitionValuesMaps(); + TransitionSet mParent = null; + + // Per-animator information used for later canceling when future transitions overlap + private static ThreadLocal> sRunningAnimators = + new ThreadLocal>(); + + // Scene Root is set at createAnimator() time in the cloned Transition + ViewGroup mSceneRoot = null; + + // Whether removing views from their parent is possible. This is only for views + // in the start scene, which are no longer in the view hierarchy. This property + // is determined by whether the previous Scene was created from a layout + // resource, and thus the views from the exited scene are going away anyway + // and can be removed as necessary to achieve a particular effect, such as + // removing them from parents to add them to overlays. + boolean mCanRemoveViews = false; + + // Track all animators in use in case the transition gets canceled and needs to + // cancel running animators + private ArrayList mCurrentAnimators = new ArrayList(); + + // Number of per-target instances of this Transition currently running. This count is + // determined by calls to start() and end() + int mNumInstances = 0; + + // Whether this transition is currently paused, due to a call to pause() + boolean mPaused = false; + + // Whether this transition has ended. Used to avoid pause/resume on transitions + // that have completed + private boolean mEnded = false; + + // The set of listeners to be sent transition lifecycle events. + ArrayList mListeners = null; + + // The set of animators collected from calls to createAnimator(), + // to be run in runAnimators() + ArrayList mAnimators = new ArrayList(); + + /** + * Constructs a Transition object with no target objects. A transition with + * no targets defaults to running on all target objects in the scene hierarchy + * (if the transition is not contained in a TransitionSet), or all target + * objects passed down from its parent (if it is in a TransitionSet). + */ + public Transition() {} + + /** + * Sets the duration of this transition. By default, there is no duration + * (indicated by a negative number), which means that the Animator created by + * the transition will have its own specified duration. If the duration of a + * Transition is set, that duration will override the Animator duration. + * + * @param duration The length of the animation, in milliseconds. + * @return This transition object. + * @attr ref android.R.styleable#Transition_duration + */ + public Transition setDuration(long duration) { + mDuration = duration; + return this; + } + + /** + * Returns the duration set on this transition. If no duration has been set, + * the returned value will be negative, indicating that resulting animators will + * retain their own durations. + * + * @return The duration set on this transition, in milliseconds, if one has been + * set, otherwise returns a negative number. + */ + public long getDuration() { + return mDuration; + } + + /** + * Sets the startDelay of this transition. By default, there is no delay + * (indicated by a negative number), which means that the Animator created by + * the transition will have its own specified startDelay. If the delay of a + * Transition is set, that delay will override the Animator delay. + * + * @param startDelay The length of the delay, in milliseconds. + * @return This transition object. + * @attr ref android.R.styleable#Transition_startDelay + */ + public Transition setStartDelay(long startDelay) { + mStartDelay = startDelay; + return this; + } + + /** + * Returns the startDelay set on this transition. If no startDelay has been set, + * the returned value will be negative, indicating that resulting animators will + * retain their own startDelays. + * + * @return The startDelay set on this transition, in milliseconds, if one has + * been set, otherwise returns a negative number. + */ + public long getStartDelay() { + return mStartDelay; + } + + /** + * Sets the interpolator of this transition. By default, the interpolator + * is null, which means that the Animator created by the transition + * will have its own specified interpolator. If the interpolator of a + * Transition is set, that interpolator will override the Animator interpolator. + * + * @param interpolator The time interpolator used by the transition + * @return This transition object. + * @attr ref android.R.styleable#Transition_interpolator + */ + public Transition setInterpolator(TimeInterpolator interpolator) { + mInterpolator = interpolator; + return this; + } + + /** + * Returns the interpolator set on this transition. If no interpolator has been set, + * the returned value will be null, indicating that resulting animators will + * retain their own interpolators. + * + * @return The interpolator set on this transition, if one has been set, otherwise + * returns null. + */ + public TimeInterpolator getInterpolator() { + return mInterpolator; + } + + /** + * Returns the set of property names used stored in the {@link TransitionValues} + * object passed into {@link #captureStartValues(TransitionValues)} that + * this transition cares about for the purposes of canceling overlapping animations. + * When any transition is started on a given scene root, all transitions + * currently running on that same scene root are checked to see whether the + * properties on which they based their animations agree with the end values of + * the same properties in the new transition. If the end values are not equal, + * then the old animation is canceled since the new transition will start a new + * animation to these new values. If the values are equal, the old animation is + * allowed to continue and no new animation is started for that transition. + * + *

    A transition does not need to override this method. However, not doing so + * will mean that the cancellation logic outlined in the previous paragraph + * will be skipped for that transition, possibly leading to artifacts as + * old transitions and new transitions on the same targets run in parallel, + * animating views toward potentially different end values.

    + * + * @return An array of property names as described in the class documentation for + * {@link TransitionValues}. The default implementation returns null. + */ + public String[] getTransitionProperties() { + return null; + } + + /** + * This method creates an animation that will be run for this transition + * given the information in the startValues and endValues structures captured + * earlier for the start and end scenes. Subclasses of Transition should override + * this method. The method should only be called by the transition system; it is + * not intended to be called from external classes. + * + *

    This method is called by the transition's parent (all the way up to the + * topmost Transition in the hierarchy) with the sceneRoot and start/end + * values that the transition may need to set up initial target values + * and construct an appropriate animation. For example, if an overall + * Transition is a {@link TransitionSet} consisting of several + * child transitions in sequence, then some of the child transitions may + * want to set initial values on target views prior to the overall + * Transition commencing, to put them in an appropriate state for the + * delay between that start and the child Transition start time. For + * example, a transition that fades an item in may wish to set the starting + * alpha value to 0, to avoid it blinking in prior to the transition + * actually starting the animation. This is necessary because the scene + * change that triggers the Transition will automatically set the end-scene + * on all target views, so a Transition that wants to animate from a + * different value should set that value prior to returning from this method.

    + * + *

    Additionally, a Transition can perform logic to determine whether + * the transition needs to run on the given target and start/end values. + * For example, a transition that resizes objects on the screen may wish + * to avoid running for views which are not present in either the start + * or end scenes.

    + * + *

    If there is an animator created and returned from this method, the + * transition mechanism will apply any applicable duration, startDelay, + * and interpolator to that animation and start it. A return value of + * null indicates that no animation should run. The default + * implementation returns null.

    + * + *

    The method is called for every applicable target object, which is + * stored in the {@link TransitionValues#view} field.

    + * + * + * @param sceneRoot The root of the transition hierarchy. + * @param startValues The values for a specific target in the start scene. + * @param endValues The values for the target in the end scene. + * @return A Animator to be started at the appropriate time in the + * overall transition for this scene change. A null value means no animation + * should be run. + */ + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + return null; + } + + /** + * This method, essentially a wrapper around all calls to createAnimator for all + * possible target views, is called with the entire set of start/end + * values. The implementation in Transition iterates through these lists + * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)} + * with each set of start/end values on this transition. The + * TransitionSet subclass overrides this method and delegates it to + * each of its children in succession. + * + * @hide + */ + protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, + TransitionValuesMaps endValues) { + if (DBG) { + Log.d(LOG_TAG, "createAnimators() for " + this); + } + ArrayMap endCopy = + new ArrayMap(endValues.viewValues); + SparseArray endIdCopy = + new SparseArray(endValues.idValues.size()); + for (int i = 0; i < endValues.idValues.size(); ++i) { + int id = endValues.idValues.keyAt(i); + endIdCopy.put(id, endValues.idValues.valueAt(i)); + } + LongSparseArray endItemIdCopy = + new LongSparseArray(endValues.itemIdValues.size()); + for (int i = 0; i < endValues.itemIdValues.size(); ++i) { + long id = endValues.itemIdValues.keyAt(i); + endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i)); + } + // Walk through the start values, playing everything we find + // Remove from the end set as we go + ArrayList startValuesList = new ArrayList(); + ArrayList endValuesList = new ArrayList(); + for (View view : startValues.viewValues.keySet()) { + TransitionValues start = null; + TransitionValues end = null; + boolean isInListView = false; + if (view.getParent() instanceof ListView) { + isInListView = true; + } + if (!isInListView) { + int id = view.getId(); + start = startValues.viewValues.get(view) != null ? + startValues.viewValues.get(view) : startValues.idValues.get(id); + if (endValues.viewValues.get(view) != null) { + end = endValues.viewValues.get(view); + endCopy.remove(view); + } else if (id != View.NO_ID) { + end = endValues.idValues.get(id); + View removeView = null; + for (View viewToRemove : endCopy.keySet()) { + if (viewToRemove.getId() == id) { + removeView = viewToRemove; + } + } + if (removeView != null) { + endCopy.remove(removeView); + } + } + endIdCopy.remove(id); + if (isValidTarget(view, id)) { + startValuesList.add(start); + endValuesList.add(end); + } + } else { + ListView parent = (ListView) view.getParent(); + if (parent.getAdapter().hasStableIds()) { + int position = parent.getPositionForView(view); + long itemId = parent.getItemIdAtPosition(position); + start = startValues.itemIdValues.get(itemId); + endItemIdCopy.remove(itemId); + // TODO: deal with targetIDs for itemIDs for ListView items + startValuesList.add(start); + endValuesList.add(end); + } + } + } + int startItemIdCopySize = startValues.itemIdValues.size(); + for (int i = 0; i < startItemIdCopySize; ++i) { + long id = startValues.itemIdValues.keyAt(i); + if (isValidTarget(null, id)) { + TransitionValues start = startValues.itemIdValues.get(id); + TransitionValues end = endValues.itemIdValues.get(id); + endItemIdCopy.remove(id); + startValuesList.add(start); + endValuesList.add(end); + } + } + // Now walk through the remains of the end set + for (View view : endCopy.keySet()) { + int id = view.getId(); + if (isValidTarget(view, id)) { + TransitionValues start = startValues.viewValues.get(view) != null ? + startValues.viewValues.get(view) : startValues.idValues.get(id); + TransitionValues end = endCopy.get(view); + endIdCopy.remove(id); + startValuesList.add(start); + endValuesList.add(end); + } + } + int endIdCopySize = endIdCopy.size(); + for (int i = 0; i < endIdCopySize; ++i) { + int id = endIdCopy.keyAt(i); + if (isValidTarget(null, id)) { + TransitionValues start = startValues.idValues.get(id); + TransitionValues end = endIdCopy.get(id); + startValuesList.add(start); + endValuesList.add(end); + } + } + int endItemIdCopySize = endItemIdCopy.size(); + for (int i = 0; i < endItemIdCopySize; ++i) { + long id = endItemIdCopy.keyAt(i); + // TODO: Deal with targetIDs and itemIDs + TransitionValues start = startValues.itemIdValues.get(id); + TransitionValues end = endItemIdCopy.get(id); + startValuesList.add(start); + endValuesList.add(end); + } + ArrayMap runningAnimators = getRunningAnimators(); + for (int i = 0; i < startValuesList.size(); ++i) { + TransitionValues start = startValuesList.get(i); + TransitionValues end = endValuesList.get(i); + // Only bother trying to animate with values that differ between start/end + if (start != null || end != null) { + if (start == null || !start.equals(end)) { + if (DBG) { + View view = (end != null) ? end.view : start.view; + Log.d(LOG_TAG, " differing start/end values for view " + + view); + if (start == null || end == null) { + Log.d(LOG_TAG, " " + ((start == null) ? + "start null, end non-null" : "start non-null, end null")); + } else { + for (String key : start.values.keySet()) { + Object startValue = start.values.get(key); + Object endValue = end.values.get(key); + if (startValue != endValue && !startValue.equals(endValue)) { + Log.d(LOG_TAG, " " + key + ": start(" + startValue + + "), end(" + endValue +")"); + } + } + } + } + // TODO: what to do about targetIds and itemIds? + Animator animator = createAnimator(sceneRoot, start, end); + if (animator != null) { + // Save animation info for future cancellation purposes + View view = null; + TransitionValues infoValues = null; + if (end != null) { + view = end.view; + String[] properties = getTransitionProperties(); + if (view != null && properties != null && properties.length > 0) { + infoValues = new TransitionValues(); + infoValues.view = view; + TransitionValues newValues = endValues.viewValues.get(view); + if (newValues != null) { + for (int j = 0; j < properties.length; ++j) { + infoValues.values.put(properties[j], + newValues.values.get(properties[j])); + } + } + int numExistingAnims = runningAnimators.size(); + for (int j = 0; j < numExistingAnims; ++j) { + Animator anim = runningAnimators.keyAt(j); + AnimationInfo info = runningAnimators.get(anim); + if (info.values != null && info.view == view && + ((info.name == null && getName() == null) || + info.name.equals(getName()))) { + if (info.values.equals(infoValues)) { + // Favor the old animator + animator = null; + break; + } + } + } + } + } else { + view = (start != null) ? start.view : null; + } + if (animator != null) { + AnimationInfo info = new AnimationInfo(view, getName(), infoValues); + runningAnimators.put(animator, info); + mAnimators.add(animator); + } + } + } + } + } + } + + /** + * Internal utility method for checking whether a given view/id + * is valid for this transition, where "valid" means that either + * the Transition has no target/targetId list (the default, in which + * cause the transition should act on all views in the hiearchy), or + * the given view is in the target list or the view id is in the + * targetId list. If the target parameter is null, then the target list + * is not checked (this is in the case of ListView items, where the + * views are ignored and only the ids are used). + */ + boolean isValidTarget(View target, long targetId) { + if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) { + return false; + } + if (mTargetExcludes != null && mTargetExcludes.contains(target)) { + return false; + } + if (mTargetTypeExcludes != null && target != null) { + int numTypes = mTargetTypeExcludes.size(); + for (int i = 0; i < numTypes; ++i) { + Class type = mTargetTypeExcludes.get(i); + if (type.isInstance(target)) { + return false; + } + } + } + if (mTargetIds.size() == 0 && mTargets.size() == 0) { + return true; + } + if (mTargetIds.size() > 0) { + for (int i = 0; i < mTargetIds.size(); ++i) { + if (mTargetIds.get(i) == targetId) { + return true; + } + } + } + if (target != null && mTargets.size() > 0) { + for (int i = 0; i < mTargets.size(); ++i) { + if (mTargets.get(i) == target) { + return true; + } + } + } + return false; + } + + private static ArrayMap getRunningAnimators() { + ArrayMap runningAnimators = sRunningAnimators.get(); + if (runningAnimators == null) { + runningAnimators = new ArrayMap(); + sRunningAnimators.set(runningAnimators); + } + return runningAnimators; + } + + /** + * This is called internally once all animations have been set up by the + * transition hierarchy. \ + * + * @hide + */ + protected void runAnimators() { + if (DBG) { + Log.d(LOG_TAG, "runAnimators() on " + this); + } + start(); + ArrayMap runningAnimators = getRunningAnimators(); + // Now start every Animator that was previously created for this transition + for (Animator anim : mAnimators) { + if (DBG) { + Log.d(LOG_TAG, " anim: " + anim); + } + if (runningAnimators.containsKey(anim)) { + start(); + runAnimator(anim, runningAnimators); + } + } + mAnimators.clear(); + end(); + } + + private void runAnimator(Animator animator, + final ArrayMap runningAnimators) { + if (animator != null) { + // TODO: could be a single listener instance for all of them since it uses the param + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mCurrentAnimators.add(animation); + } + @Override + public void onAnimationEnd(Animator animation) { + runningAnimators.remove(animation); + mCurrentAnimators.remove(animation); + } + }); + animate(animator); + } + } + + /** + * Captures the values in the start scene for the properties that this + * transition monitors. These values are then passed as the startValues + * structure in a later call to + * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. + * The main concern for an implementation is what the + * properties are that the transition cares about and what the values are + * for all of those properties. The start and end values will be compared + * later during the + * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} + * method to determine what, if any, animations, should be run. + * + *

    Subclasses must implement this method. The method should only be called by the + * transition system; it is not intended to be called from external classes.

    + * + * @param transitionValues The holder for any values that the Transition + * wishes to store. Values are stored in the values field + * of this TransitionValues object and are keyed from + * a String value. For example, to store a view's rotation value, + * a transition might call + * transitionValues.values.put("appname:transitionname:rotation", + * view.getRotation()). The target view will already be stored in + * the transitionValues structure when this method is called. + * + * @see #captureEndValues(TransitionValues) + * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) + */ + public abstract void captureStartValues(TransitionValues transitionValues); + + /** + * Captures the values in the end scene for the properties that this + * transition monitors. These values are then passed as the endValues + * structure in a later call to + * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. + * The main concern for an implementation is what the + * properties are that the transition cares about and what the values are + * for all of those properties. The start and end values will be compared + * later during the + * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} + * method to determine what, if any, animations, should be run. + * + *

    Subclasses must implement this method. The method should only be called by the + * transition system; it is not intended to be called from external classes.

    + * + * @param transitionValues The holder for any values that the Transition + * wishes to store. Values are stored in the values field + * of this TransitionValues object and are keyed from + * a String value. For example, to store a view's rotation value, + * a transition might call + * transitionValues.values.put("appname:transitionname:rotation", + * view.getRotation()). The target view will already be stored in + * the transitionValues structure when this method is called. + * + * @see #captureStartValues(TransitionValues) + * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) + */ + public abstract void captureEndValues(TransitionValues transitionValues); + + /** + * Adds the id of a target view that this Transition is interested in + * animating. By default, there are no targetIds, and a Transition will + * listen for changes on every view in the hierarchy below the sceneRoot + * of the Scene being transitioned into. Setting targetIds constrains + * the Transition to only listen for, and act on, views with these IDs. + * Views with different IDs, or no IDs whatsoever, will be ignored. + * + *

    Note that using ids to specify targets implies that ids should be unique + * within the view hierarchy underneat the scene root.

    + * + * @see View#getId() + * @param targetId The id of a target view, must be a positive number. + * @return The Transition to which the targetId is added. + * Returning the same object makes it easier to chain calls during + * construction, such as + * transitionSet.addTransitions(new Fade()).addTarget(someId); + */ + public Transition addTarget(int targetId) { + if (targetId > 0) { + mTargetIds.add(targetId); + } + return this; + } + + /** + * Removes the given targetId from the list of ids that this Transition + * is interested in animating. + * + * @param targetId The id of a target view, must be a positive number. + * @return The Transition from which the targetId is removed. + * Returning the same object makes it easier to chain calls during + * construction, such as + * transitionSet.addTransitions(new Fade()).removeTargetId(someId); + */ + public Transition removeTarget(int targetId) { + if (targetId > 0) { + mTargetIds.remove(targetId); + } + return this; + } + + /** + * Whether to add the given id to the list of target ids to exclude from this + * transition. The exclude parameter specifies whether the target + * should be added to or removed from the excluded list. + * + *

    Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

    + * + * @see #excludeChildren(int, boolean) + * @see #excludeTarget(View, boolean) + * @see #excludeTarget(Class, boolean) + * + * @param targetId The id of a target to ignore when running this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded targets. + * @return This transition object. + */ + public Transition excludeTarget(int targetId, boolean exclude) { + mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude); + return this; + } + + /** + * Whether to add the children of the given id to the list of targets to exclude + * from this transition. The exclude parameter specifies whether + * the children of the target should be added to or removed from the excluded list. + * Excluding children in this way provides a simple mechanism for excluding all + * children of specific targets, rather than individually excluding each + * child individually. + * + *

    Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

    + * + * @see #excludeTarget(int, boolean) + * @see #excludeChildren(View, boolean) + * @see #excludeChildren(Class, boolean) + * + * @param targetId The id of a target whose children should be ignored when running + * this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded-child targets. + * @return This transition object. + */ + public Transition excludeChildren(int targetId, boolean exclude) { + mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude); + return this; + } + + /** + * Utility method to manage the boilerplate code that is the same whether we + * are excluding targets or their children. + */ + private ArrayList excludeId(ArrayList list, int targetId, boolean exclude) { + if (targetId > 0) { + if (exclude) { + list = ArrayListManager.add(list, targetId); + } else { + list = ArrayListManager.remove(list, targetId); + } + } + return list; + } + + /** + * Whether to add the given target to the list of targets to exclude from this + * transition. The exclude parameter specifies whether the target + * should be added to or removed from the excluded list. + * + *

    Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

    + * + * @see #excludeChildren(View, boolean) + * @see #excludeTarget(int, boolean) + * @see #excludeTarget(Class, boolean) + * + * @param target The target to ignore when running this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded targets. + * @return This transition object. + */ + public Transition excludeTarget(View target, boolean exclude) { + mTargetExcludes = excludeView(mTargetExcludes, target, exclude); + return this; + } + + /** + * Whether to add the children of given target to the list of target children + * to exclude from this transition. The exclude parameter specifies + * whether the target should be added to or removed from the excluded list. + * + *

    Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

    + * + * @see #excludeTarget(View, boolean) + * @see #excludeChildren(int, boolean) + * @see #excludeChildren(Class, boolean) + * + * @param target The target to ignore when running this transition. + * @param exclude Whether to add the target to or remove the target from the + * current list of excluded targets. + * @return This transition object. + */ + public Transition excludeChildren(View target, boolean exclude) { + mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude); + return this; + } + + /** + * Utility method to manage the boilerplate code that is the same whether we + * are excluding targets or their children. + */ + private ArrayList excludeView(ArrayList list, View target, boolean exclude) { + if (target != null) { + if (exclude) { + list = ArrayListManager.add(list, target); + } else { + list = ArrayListManager.remove(list, target); + } + } + return list; + } + + /** + * Whether to add the given type to the list of types to exclude from this + * transition. The exclude parameter specifies whether the target + * type should be added to or removed from the excluded list. + * + *

    Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

    + * + * @see #excludeChildren(Class, boolean) + * @see #excludeTarget(int, boolean) + * @see #excludeTarget(View, boolean) + * + * @param type The type to ignore when running this transition. + * @param exclude Whether to add the target type to or remove it from the + * current list of excluded target types. + * @return This transition object. + */ + public Transition excludeTarget(Class type, boolean exclude) { + mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude); + return this; + } + + /** + * Whether to add the given type to the list of types whose children should + * be excluded from this transition. The exclude parameter + * specifies whether the target type should be added to or removed from + * the excluded list. + * + *

    Excluding targets is a general mechanism for allowing transitions to run on + * a view hierarchy while skipping target views that should not be part of + * the transition. For example, you may want to avoid animating children + * of a specific ListView or Spinner. Views can be excluded either by their + * id, or by their instance reference, or by the Class of that view + * (eg, {@link Spinner}).

    + * + * @see #excludeTarget(Class, boolean) + * @see #excludeChildren(int, boolean) + * @see #excludeChildren(View, boolean) + * + * @param type The type to ignore when running this transition. + * @param exclude Whether to add the target type to or remove it from the + * current list of excluded target types. + * @return This transition object. + */ + public Transition excludeChildren(Class type, boolean exclude) { + mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude); + return this; + } + + /** + * Utility method to manage the boilerplate code that is the same whether we + * are excluding targets or their children. + */ + private ArrayList excludeType(ArrayList list, Class type, boolean exclude) { + if (type != null) { + if (exclude) { + list = ArrayListManager.add(list, type); + } else { + list = ArrayListManager.remove(list, type); + } + } + return list; + } + + /** + * Sets the target view instances that this Transition is interested in + * animating. By default, there are no targets, and a Transition will + * listen for changes on every view in the hierarchy below the sceneRoot + * of the Scene being transitioned into. Setting targets constrains + * the Transition to only listen for, and act on, these views. + * All other views will be ignored. + * + *

    The target list is like the {@link #addTarget(int) targetId} + * list except this list specifies the actual View instances, not the ids + * of the views. This is an important distinction when scene changes involve + * view hierarchies which have been inflated separately; different views may + * share the same id but not actually be the same instance. If the transition + * should treat those views as the same, then {@link #addTarget(int)} should be used + * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve + * changes all within the same view hierarchy, among views which do not + * necessarily have ids set on them, then the target list of views may be more + * convenient.

    + * + * @see #addTarget(int) + * @param target A View on which the Transition will act, must be non-null. + * @return The Transition to which the target is added. + * Returning the same object makes it easier to chain calls during + * construction, such as + * transitionSet.addTransitions(new Fade()).addTarget(someView); + */ + public Transition addTarget(View target) { + mTargets.add(target); + return this; + } + + /** + * Removes the given target from the list of targets that this Transition + * is interested in animating. + * + * @param target The target view, must be non-null. + * @return Transition The Transition from which the target is removed. + * Returning the same object makes it easier to chain calls during + * construction, such as + * transitionSet.addTransitions(new Fade()).removeTarget(someView); + */ + public Transition removeTarget(View target) { + if (target != null) { + mTargets.remove(target); + } + return this; + } + + /** + * Returns the array of target IDs that this transition limits itself to + * tracking and animating. If the array is null for both this method and + * {@link #getTargets()}, then this transition is + * not limited to specific views, and will handle changes to any views + * in the hierarchy of a scene change. + * + * @return the list of target IDs + */ + public List getTargetIds() { + return mTargetIds; + } + + /** + * Returns the array of target views that this transition limits itself to + * tracking and animating. If the array is null for both this method and + * {@link #getTargetIds()}, then this transition is + * not limited to specific views, and will handle changes to any views + * in the hierarchy of a scene change. + * + * @return the list of target views + */ + public List getTargets() { + return mTargets; + } + + /** + * Recursive method that captures values for the given view and the + * hierarchy underneath it. + * @param sceneRoot The root of the view hierarchy being captured + * @param start true if this capture is happening before the scene change, + * false otherwise + */ + void captureValues(ViewGroup sceneRoot, boolean start) { + if (start) { + mStartValues.viewValues.clear(); + mStartValues.idValues.clear(); + mStartValues.itemIdValues.clear(); + } else { + mEndValues.viewValues.clear(); + mEndValues.idValues.clear(); + mEndValues.itemIdValues.clear(); + } + if (mTargetIds.size() > 0 || mTargets.size() > 0) { + if (mTargetIds.size() > 0) { + for (int i = 0; i < mTargetIds.size(); ++i) { + int id = mTargetIds.get(i); + View view = sceneRoot.findViewById(id); + if (view != null) { + TransitionValues values = new TransitionValues(); + values.view = view; + if (start) { + captureStartValues(values); + } else { + captureEndValues(values); + } + if (start) { + mStartValues.viewValues.put(view, values); + if (id >= 0) { + mStartValues.idValues.put(id, values); + } + } else { + mEndValues.viewValues.put(view, values); + if (id >= 0) { + mEndValues.idValues.put(id, values); + } + } + } + } + } + if (mTargets.size() > 0) { + for (int i = 0; i < mTargets.size(); ++i) { + View view = mTargets.get(i); + if (view != null) { + TransitionValues values = new TransitionValues(); + values.view = view; + if (start) { + captureStartValues(values); + } else { + captureEndValues(values); + } + if (start) { + mStartValues.viewValues.put(view, values); + } else { + mEndValues.viewValues.put(view, values); + } + } + } + } + } else { + captureHierarchy(sceneRoot, start); + } + } + + /** + * Recursive method which captures values for an entire view hierarchy, + * starting at some root view. Transitions without targetIDs will use this + * method to capture values for all possible views. + * + * @param view The view for which to capture values. Children of this View + * will also be captured, recursively down to the leaf nodes. + * @param start true if values are being captured in the start scene, false + * otherwise. + */ + private void captureHierarchy(View view, boolean start) { + if (view == null) { + return; + } + boolean isListViewItem = false; + if (view.getParent() instanceof ListView) { + isListViewItem = true; + } + if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) { + // ignore listview children unless we can track them with stable IDs + return; + } + int id = View.NO_ID; + long itemId = View.NO_ID; + if (!isListViewItem) { + id = view.getId(); + } else { + ListView listview = (ListView) view.getParent(); + int position = listview.getPositionForView(view); + itemId = listview.getItemIdAtPosition(position); + view.setHasTransientState(true); + } + if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) { + return; + } + if (mTargetExcludes != null && mTargetExcludes.contains(view)) { + return; + } + if (mTargetTypeExcludes != null && view != null) { + int numTypes = mTargetTypeExcludes.size(); + for (int i = 0; i < numTypes; ++i) { + if (mTargetTypeExcludes.get(i).isInstance(view)) { + return; + } + } + } + TransitionValues values = new TransitionValues(); + values.view = view; + if (start) { + captureStartValues(values); + } else { + captureEndValues(values); + } + if (start) { + if (!isListViewItem) { + mStartValues.viewValues.put(view, values); + if (id >= 0) { + mStartValues.idValues.put((int) id, values); + } + } else { + mStartValues.itemIdValues.put(itemId, values); + } + } else { + if (!isListViewItem) { + mEndValues.viewValues.put(view, values); + if (id >= 0) { + mEndValues.idValues.put((int) id, values); + } + } else { + mEndValues.itemIdValues.put(itemId, values); + } + } + if (view instanceof ViewGroup) { + // Don't traverse child hierarchy if there are any child-excludes on this view + if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) { + return; + } + if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) { + return; + } + if (mTargetTypeChildExcludes != null && view != null) { + int numTypes = mTargetTypeChildExcludes.size(); + for (int i = 0; i < numTypes; ++i) { + if (mTargetTypeChildExcludes.get(i).isInstance(view)) { + return; + } + } + } + ViewGroup parent = (ViewGroup) view; + for (int i = 0; i < parent.getChildCount(); ++i) { + captureHierarchy(parent.getChildAt(i), start); + } + } + } + + /** + * This method can be called by transitions to get the TransitionValues for + * any particular view during the transition-playing process. This might be + * necessary, for example, to query the before/after state of related views + * for a given transition. + */ + public TransitionValues getTransitionValues(View view, boolean start) { + if (mParent != null) { + return mParent.getTransitionValues(view, start); + } + TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues; + TransitionValues values = valuesMaps.viewValues.get(view); + if (values == null) { + int id = view.getId(); + if (id >= 0) { + values = valuesMaps.idValues.get(id); + } + if (values == null && view.getParent() instanceof ListView) { + ListView listview = (ListView) view.getParent(); + int position = listview.getPositionForView(view); + long itemId = listview.getItemIdAtPosition(position); + values = valuesMaps.itemIdValues.get(itemId); + } + // TODO: Doesn't handle the case where a view was parented to a + // ListView (with an itemId), but no longer is + } + return values; + } + + /** + * Pauses this transition, sending out calls to {@link + * TransitionListener#onTransitionPause(Transition)} to all listeners + * and pausing all running animators started by this transition. + * + * @hide + */ + public void pause() { + if (!mEnded) { + ArrayMap runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + anim.pause(); + } + if (mListeners != null && mListeners.size() > 0) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionPause(this); + } + } + mPaused = true; + } + } + + /** + * Resumes this transition, sending out calls to {@link + * TransitionListener#onTransitionPause(Transition)} to all listeners + * and pausing all running animators started by this transition. + * + * @hide + */ + public void resume() { + if (mPaused) { + if (!mEnded) { + ArrayMap runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + anim.resume(); + } + if (mListeners != null && mListeners.size() > 0) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionResume(this); + } + } + } + mPaused = false; + } + } + + /** + * Called by TransitionManager to play the transition. This calls + * createAnimators() to set things up and create all of the animations and then + * runAnimations() to actually start the animations. + */ + void playTransition(ViewGroup sceneRoot) { + ArrayMap runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + if (anim != null) { + AnimationInfo oldInfo = runningAnimators.get(anim); + if (oldInfo != null) { + boolean cancel = false; + TransitionValues oldValues = oldInfo.values; + View oldView = oldInfo.view; + TransitionValues newValues = mEndValues.viewValues != null ? + mEndValues.viewValues.get(oldView) : null; + if (newValues == null) { + newValues = mEndValues.idValues.get(oldView.getId()); + } + if (oldValues != null) { + // if oldValues null, then transition didn't care to stash values, + // and won't get canceled + if (newValues != null) { + 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; + } + } + } + } + if (cancel) { + if (anim.isRunning() || anim.isStarted()) { + if (DBG) { + Log.d(LOG_TAG, "Canceling anim " + anim); + } + anim.cancel(); + } else { + if (DBG) { + Log.d(LOG_TAG, "removing anim from info list: " + anim); + } + runningAnimators.remove(anim); + } + } + } + } + } + + createAnimators(sceneRoot, mStartValues, mEndValues); + runAnimators(); + } + + /** + * This is a utility method used by subclasses to handle standard parts of + * setting up and running an Animator: it sets the {@link #getDuration() + * duration} and the {@link #getStartDelay() startDelay}, starts the + * animation, and, when the animator ends, calls {@link #end()}. + * + * @param animator The Animator to be run during this transition. + * + * @hide + */ + protected void animate(Animator animator) { + // TODO: maybe pass auto-end as a boolean parameter? + if (animator == null) { + end(); + } else { + if (getDuration() >= 0) { + animator.setDuration(getDuration()); + } + if (getStartDelay() >= 0) { + animator.setStartDelay(getStartDelay()); + } + if (getInterpolator() != null) { + animator.setInterpolator(getInterpolator()); + } + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + end(); + animation.removeListener(this); + } + }); + animator.start(); + } + } + + /** + * This method is called automatically by the transition and + * TransitionSet classes prior to a Transition subclass starting; + * subclasses should not need to call it directly. + * + * @hide + */ + protected void start() { + if (mNumInstances == 0) { + if (mListeners != null && mListeners.size() > 0) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionStart(this); + } + } + mEnded = false; + } + mNumInstances++; + } + + /** + * This method is called automatically by the Transition and + * TransitionSet classes when a transition finishes, either because + * a transition did nothing (returned a null Animator from + * {@link Transition#createAnimator(ViewGroup, TransitionValues, + * TransitionValues)}) or because the transition returned a valid + * Animator and end() was called in the onAnimationEnd() + * callback of the AnimatorListener. + * + * @hide + */ + protected void end() { + --mNumInstances; + if (mNumInstances == 0) { + if (mListeners != null && mListeners.size() > 0) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionEnd(this); + } + } + for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) { + TransitionValues tv = mStartValues.itemIdValues.valueAt(i); + View v = tv.view; + if (v.hasTransientState()) { + v.setHasTransientState(false); + } + } + for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) { + TransitionValues tv = mEndValues.itemIdValues.valueAt(i); + View v = tv.view; + if (v.hasTransientState()) { + v.setHasTransientState(false); + } + } + mEnded = true; + } + } + + /** + * This method cancels a transition that is currently running. + * + * @hide + */ + protected void cancel() { + int numAnimators = mCurrentAnimators.size(); + for (int i = numAnimators - 1; i >= 0; i--) { + Animator animator = mCurrentAnimators.get(i); + animator.cancel(); + } + if (mListeners != null && mListeners.size() > 0) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionCancel(this); + } + } + } + + /** + * Adds a listener to the set of listeners that are sent events through the + * life of an animation, such as start, repeat, and end. + * + * @param listener the listener to be added to the current set of listeners + * for this animation. + * @return This transition object. + */ + public Transition addListener(TransitionListener listener) { + if (mListeners == null) { + mListeners = new ArrayList(); + } + mListeners.add(listener); + return this; + } + + /** + * Removes a listener from the set listening to this animation. + * + * @param listener the listener to be removed from the current set of + * listeners for this transition. + * @return This transition object. + */ + public Transition removeListener(TransitionListener listener) { + if (mListeners == null) { + return this; + } + mListeners.remove(listener); + if (mListeners.size() == 0) { + mListeners = null; + } + return this; + } + + Transition setSceneRoot(ViewGroup sceneRoot) { + mSceneRoot = sceneRoot; + return this; + } + + void setCanRemoveViews(boolean canRemoveViews) { + mCanRemoveViews = canRemoveViews; + } + + @Override + public String toString() { + return toString(""); + } + + @Override + public Transition clone() { + Transition clone = null; + try { + clone = (Transition) super.clone(); + clone.mAnimators = new ArrayList(); + clone.mStartValues = new TransitionValuesMaps(); + clone.mEndValues = new TransitionValuesMaps(); + } catch (CloneNotSupportedException e) {} + + return clone; + } + + /** + * Returns the name of this Transition. This name is used internally to distinguish + * between different transitions to determine when interrupting transitions overlap. + * For example, a ChangeBounds running on the same target view as another ChangeBounds + * should determine whether the old transition is animating to different end values + * and should be canceled in favor of the new transition. + * + *

    By default, a Transition's name is simply the value of {@link Class#getName()}, + * but subclasses are free to override and return something different.

    + * + * @return The name of this transition. + */ + public String getName() { + return mName; + } + + String toString(String indent) { + String result = indent + getClass().getSimpleName() + "@" + + Integer.toHexString(hashCode()) + ": "; + if (mDuration != -1) { + result += "dur(" + mDuration + ") "; + } + if (mStartDelay != -1) { + result += "dly(" + mStartDelay + ") "; + } + if (mInterpolator != null) { + result += "interp(" + mInterpolator + ") "; + } + if (mTargetIds.size() > 0 || mTargets.size() > 0) { + result += "tgts("; + if (mTargetIds.size() > 0) { + for (int i = 0; i < mTargetIds.size(); ++i) { + if (i > 0) { + result += ", "; + } + result += mTargetIds.get(i); + } + } + if (mTargets.size() > 0) { + for (int i = 0; i < mTargets.size(); ++i) { + if (i > 0) { + result += ", "; + } + result += mTargets.get(i); + } + } + result += ")"; + } + return result; + } + + /** + * A transition listener receives notifications from a transition. + * Notifications indicate transition lifecycle events. + */ + public static interface TransitionListener { + /** + * Notification about the start of the transition. + * + * @param transition The started transition. + */ + void onTransitionStart(Transition transition); + + /** + * Notification about the end of the transition. Canceled transitions + * will always notify listeners of both the cancellation and end + * events. That is, {@link #onTransitionEnd(Transition)} is always called, + * regardless of whether the transition was canceled or played + * through to completion. + * + * @param transition The transition which reached its end. + */ + void onTransitionEnd(Transition transition); + + /** + * Notification about the cancellation of the transition. + * Note that cancel may be called by a parent {@link TransitionSet} on + * a child transition which has not yet started. This allows the child + * transition to restore state on target objects which was set at + * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) + * createAnimator()} time. + * + * @param transition The transition which was canceled. + */ + void onTransitionCancel(Transition transition); + + /** + * Notification when a transition is paused. + * Note that createAnimator() may be called by a parent {@link TransitionSet} on + * a child transition which has not yet started. This allows the child + * transition to restore state on target objects which was set at + * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) + * createAnimator()} time. + * + * @param transition The transition which was paused. + */ + void onTransitionPause(Transition transition); + + /** + * Notification when a transition is resumed. + * Note that resume() may be called by a parent {@link TransitionSet} on + * a child transition which has not yet started. This allows the child + * transition to restore state which may have changed in an earlier call + * to {@link #onTransitionPause(Transition)}. + * + * @param transition The transition which was resumed. + */ + void onTransitionResume(Transition transition); + } + + /** + * Utility adapter class to avoid having to override all three methods + * whenever someone just wants to listen for a single event. + * + * @hide + * */ + public static class TransitionListenerAdapter implements TransitionListener { + @Override + public void onTransitionStart(Transition transition) { + } + + @Override + public void onTransitionEnd(Transition transition) { + } + + @Override + public void onTransitionCancel(Transition transition) { + } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { + } + } + + /** + * Holds information about each animator used when a new transition starts + * while other transitions are still running to determine whether a running + * animation should be canceled or a new animation noop'd. The structure holds + * information about the state that an animation is going to, to be compared to + * end state of a new animation. + */ + private static class AnimationInfo { + View view; + String name; + TransitionValues values; + + AnimationInfo(View view, String name, TransitionValues values) { + this.view = view; + this.name = name; + this.values = values; + } + } + + /** + * Utility class for managing typed ArrayLists efficiently. In particular, this + * can be useful for lists that we don't expect to be used often (eg, the exclude + * lists), so we'd like to keep them nulled out by default. This causes the code to + * become tedious, with constant null checks, code to allocate when necessary, + * and code to null out the reference when the list is empty. This class encapsulates + * all of that functionality into simple add()/remove() methods which perform the + * necessary checks, allocation/null-out as appropriate, and return the + * resulting list. + */ + private static class ArrayListManager { + + /** + * Add the specified item to the list, returning the resulting list. + * The returned list can either the be same list passed in or, if that + * list was null, the new list that was created. + * + * Note that the list holds unique items; if the item already exists in the + * list, the list is not modified. + */ + static ArrayList add(ArrayList list, T item) { + if (list == null) { + list = new ArrayList(); + } + if (!list.contains(item)) { + list.add(item); + } + return list; + } + + /** + * Remove the specified item from the list, returning the resulting list. + * The returned list can either the be same list passed in or, if that + * list becomes empty as a result of the remove(), the new list was created. + */ + static ArrayList remove(ArrayList list, T item) { + if (list != null) { + list.remove(item); + if (list.isEmpty()) { + list = null; + } + } + return list; + } + } + +} diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java new file mode 100644 index 0000000000000000000000000000000000000000..4af0f51a41e2a0bc04e635185fe1d27d0686830e --- /dev/null +++ b/core/java/android/transition/TransitionInflater.java @@ -0,0 +1,327 @@ +/* + * 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.transition; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.util.Xml; +import android.view.InflateException; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class inflates scenes and transitions from resource files. + * + * Information on XML resource descriptions for transitions can be found for + * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, + * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, + * and {@link android.R.styleable#TransitionManager}. + */ +public class TransitionInflater { + + // We only need one inflater for any given context. Also, this allows us to associate + // ids with unique instances per-Context, used to avoid re-inflating + // already-inflated resources into new/different instances + private static final ArrayMap sInflaterMap = + new ArrayMap(); + + private Context mContext; + // TODO: do we need id maps for transitions and transitionMgrs as well? + SparseArray mScenes = new SparseArray(); + + private TransitionInflater(Context context) { + mContext = context; + } + + /** + * Obtains the TransitionInflater from the given context. + */ + public static TransitionInflater from(Context context) { + TransitionInflater inflater = sInflaterMap.get(context); + if (inflater != null) { + return inflater; + } + inflater = new TransitionInflater(context); + sInflaterMap.put(context, inflater); + return inflater; + } + + /** + * Loads a {@link Transition} object from a resource + * + * @param resource The resource id of the transition to load + * @return The loaded Transition object + * @throws android.content.res.Resources.NotFoundException when the + * transition cannot be loaded + */ + public Transition inflateTransition(int resource) { + XmlResourceParser parser = mContext.getResources().getXml(resource); + try { + return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null); + } catch (XmlPullParserException e) { + InflateException ex = new InflateException(e.getMessage()); + ex.initCause(e); + throw ex; + } catch (IOException e) { + InflateException ex = new InflateException( + parser.getPositionDescription() + + ": " + e.getMessage()); + ex.initCause(e); + throw ex; + } finally { + parser.close(); + } + } + + /** + * Loads a {@link TransitionManager} object from a resource + * + * + * + * @param resource The resource id of the transition manager to load + * @return The loaded TransitionManager object + * @throws android.content.res.Resources.NotFoundException when the + * transition manager cannot be loaded + */ + public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) { + XmlResourceParser parser = mContext.getResources().getXml(resource); + try { + return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot); + } catch (XmlPullParserException e) { + InflateException ex = new InflateException(e.getMessage()); + ex.initCause(e); + throw ex; + } catch (IOException e) { + InflateException ex = new InflateException( + parser.getPositionDescription() + + ": " + e.getMessage()); + ex.initCause(e); + throw ex; + } finally { + parser.close(); + } + } + + // + // Transition loading + // + + private Transition createTransitionFromXml(XmlPullParser parser, + AttributeSet attrs, TransitionSet transitionSet) + throws XmlPullParserException, IOException { + + Transition transition = null; + + // Make sure we are on a start tag. + int type; + int depth = parser.getDepth(); + + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + boolean newTransition = false; + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + if ("fade".equals(name)) { + TypedArray a = mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.Fade); + int fadingMode = a.getInt(com.android.internal.R.styleable.Fade_fadingMode, + Fade.IN | Fade.OUT); + transition = new Fade(fadingMode); + newTransition = true; + } else if ("changeBounds".equals(name)) { + transition = new ChangeBounds(); + newTransition = true; + } else if ("slide".equals(name)) { + transition = new Slide(); + newTransition = true; + } else if ("autoTransition".equals(name)) { + transition = new AutoTransition(); + newTransition = true; + } else if ("recolor".equals(name)) { + transition = new Recolor(); + newTransition = true; + } else if ("transitionSet".equals(name)) { + transition = new TransitionSet(); + TypedArray a = mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.TransitionSet); + int ordering = a.getInt( + com.android.internal.R.styleable.TransitionSet_transitionOrdering, + TransitionSet.ORDERING_TOGETHER); + ((TransitionSet) transition).setOrdering(ordering); + createTransitionFromXml(parser, attrs, ((TransitionSet) transition)); + a.recycle(); + newTransition = true; + } else if ("targets".equals(name)) { + if (parser.getDepth() - 1 > depth && transition != null) { + // We're inside the child tag - add targets to the child + getTargetIds(parser, attrs, transition); + } else if (parser.getDepth() - 1 == depth && transitionSet != null) { + // add targets to the set + getTargetIds(parser, attrs, transitionSet); + } + } + if (transition != null || "targets".equals(name)) { + if (newTransition) { + loadTransition(transition, attrs); + if (transitionSet != null) { + transitionSet.addTransition(transition); + } + } + } else { + throw new RuntimeException("Unknown scene name: " + parser.getName()); + } + } + + return transition; + } + + private void getTargetIds(XmlPullParser parser, + AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { + + // Make sure we are on a start tag. + int type; + int depth = parser.getDepth(); + + ArrayList targetIds = new ArrayList(); + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + if (name.equals("target")) { + TypedArray a = mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.TransitionTarget); + int id = a.getResourceId( + com.android.internal.R.styleable.TransitionTarget_targetId, -1); + if (id >= 0) { + targetIds.add(id); + } + } else { + throw new RuntimeException("Unknown scene name: " + parser.getName()); + } + } + int numTargets = targetIds.size(); + if (numTargets > 0) { + for (int i = 0; i < numTargets; ++i) { + transition.addTarget(targetIds.get(i)); + } + } + } + + private Transition loadTransition(Transition transition, AttributeSet attrs) + throws Resources.NotFoundException { + + TypedArray a = + mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition); + long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1); + if (duration >= 0) { + transition.setDuration(duration); + } + long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1); + if (startDelay > 0) { + transition.setStartDelay(startDelay); + } + final int resID = + a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); + if (resID > 0) { + transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID)); + } + a.recycle(); + return transition; + } + + // + // TransitionManager loading + // + + private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, + AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { + + // Make sure we are on a start tag. + int type; + int depth = parser.getDepth(); + TransitionManager transitionManager = null; + + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + if (name.equals("transitionManager")) { + transitionManager = new TransitionManager(); + } else if (name.equals("transition") && (transitionManager != null)) { + loadTransition(attrs, sceneRoot, transitionManager); + } else { + throw new RuntimeException("Unknown scene name: " + parser.getName()); + } + } + return transitionManager; + } + + private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot, + TransitionManager transitionManager) throws Resources.NotFoundException { + + TypedArray a = mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.TransitionManager); + int transitionId = a.getResourceId( + com.android.internal.R.styleable.TransitionManager_transition, -1); + Scene fromScene = null, toScene = null; + int fromId = a.getResourceId( + com.android.internal.R.styleable.TransitionManager_fromScene, -1); + if (fromId >= 0) fromScene = Scene.getSceneForLayout(sceneRoot, fromId, mContext); + int toId = a.getResourceId( + com.android.internal.R.styleable.TransitionManager_toScene, -1); + if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext); + if (transitionId >= 0) { + Transition transition = inflateTransition(transitionId); + if (transition != null) { + if (fromScene != null) { + if (toScene == null){ + throw new RuntimeException("No matching toScene for given fromScene " + + "for transition ID " + transitionId); + } else { + transitionManager.setTransition(fromScene, toScene, transition); + } + } else if (toId >= 0) { + transitionManager.setTransition(toScene, transition); + } + } + } + a.recycle(); + } +} diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java new file mode 100644 index 0000000000000000000000000000000000000000..404709c6a112a27fac8e7b73e457d7cbd6ee012e --- /dev/null +++ b/core/java/android/transition/TransitionManager.java @@ -0,0 +1,367 @@ +/* + * 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.transition; + +import android.content.Context; +import android.util.ArrayMap; +import android.util.Log; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * This class manages the set of transitions that fire when there is a + * change of {@link Scene}. To use the manager, add scenes along with + * transition objects with calls to {@link #setTransition(Scene, Transition)} + * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific + * transitions for scene changes is not required; by default, a Scene change + * will use {@link AutoTransition} to do something reasonable for most + * situations. Specifying other transitions for particular scene changes is + * only necessary if the application wants different transition behavior + * in these situations. + * + *

    TransitionManagers can be declared in XML resource files inside the + * res/transition directory. TransitionManager resources consist of + * the transitionManagertag name, containing one or more + * transition tags, each of which describe the relationship of + * that transition to the from/to scene information in that tag. + * For example, here is a resource file that declares several scene + * transitions:

    + * + * {@sample development/samples/ApiDemos/res/transition/transitions_mgr.xml TransitionManager} + * + *

    For each of the fromScene and toScene attributes, + * there is a reference to a standard XML layout file. This is equivalent to + * creating a scene from a layout in code by calling + * {@link Scene#getSceneForLayout(ViewGroup, int, Context)}. For the + * transition attribute, there is a reference to a resource + * file in the res/transition directory which describes that + * transition.

    + * + * Information on XML resource descriptions for transitions can be found for + * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, + * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, + * and {@link android.R.styleable#TransitionManager}. + */ +public class TransitionManager { + // TODO: how to handle enter/exit? + + private static String LOG_TAG = "TransitionManager"; + + private static Transition sDefaultTransition = new AutoTransition(); + + ArrayMap mSceneTransitions = new ArrayMap(); + ArrayMap> mScenePairTransitions = + new ArrayMap>(); + private static ThreadLocal>>> + sRunningTransitions = + new ThreadLocal>>>(); + private static ArrayList sPendingTransitions = new ArrayList(); + + + /** + * Sets the transition to be used for any scene change for which no + * other transition is explicitly set. The initial value is + * an {@link AutoTransition} instance. + * + * @param transition The default transition to be used for scene changes. + */ + public void setDefaultTransition(Transition transition) { + sDefaultTransition = transition; + } + + /** + * Gets the current default transition. The initial value is an {@link + * AutoTransition} instance. + * + * @return The current default transition. + * @see #setDefaultTransition(Transition) + */ + public static Transition getDefaultTransition() { + return sDefaultTransition; + } + + /** + * Sets a specific transition to occur when the given scene is entered. + * + * @param scene The scene which, when applied, will cause the given + * transition to run. + * @param transition The transition that will play when the given scene is + * entered. A value of null will result in the default behavior of + * using the {@link #getDefaultTransition() default transition} instead. + */ + public void setTransition(Scene scene, Transition transition) { + mSceneTransitions.put(scene, transition); + } + + /** + * Sets a specific transition to occur when the given pair of scenes is + * exited/entered. + * + * @param fromScene The scene being exited when the given transition will + * be run + * @param toScene The scene being entered when the given transition will + * be run + * @param transition The transition that will play when the given scene is + * entered. A value of null will result in the default behavior of + * using the {@link #getDefaultTransition() default transition} instead. + */ + public void setTransition(Scene fromScene, Scene toScene, Transition transition) { + ArrayMap sceneTransitionMap = mScenePairTransitions.get(toScene); + if (sceneTransitionMap == null) { + sceneTransitionMap = new ArrayMap(); + mScenePairTransitions.put(toScene, sceneTransitionMap); + } + sceneTransitionMap.put(fromScene, transition); + } + + /** + * Returns the Transition for the given scene being entered. The result + * depends not only on the given scene, but also the scene which the + * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in. + * + * @param scene The scene being entered + * @return The Transition to be used for the given scene change. If no + * Transition was specified for this scene change, the {@link #getDefaultTransition() + * default transition} will be used instead. + */ + private Transition getTransition(Scene scene) { + Transition transition = null; + ViewGroup sceneRoot = scene.getSceneRoot(); + if (sceneRoot != null) { + // TODO: cached in Scene instead? long-term, cache in View itself + Scene currScene = Scene.getCurrentScene(sceneRoot); + if (currScene != null) { + ArrayMap sceneTransitionMap = mScenePairTransitions.get(scene); + if (sceneTransitionMap != null) { + transition = sceneTransitionMap.get(currScene); + if (transition != null) { + return transition; + } + } + } + } + transition = mSceneTransitions.get(scene); + return (transition != null) ? transition : sDefaultTransition; + } + + /** + * This is where all of the work of a transition/scene-change is + * orchestrated. This method captures the start values for the given + * transition, exits the current Scene, enters the new scene, captures + * the end values for the transition, and finally plays the + * resulting values-populated transition. + * + * @param scene The scene being entered + * @param transition The transition to play for this scene change + */ + private static void changeScene(Scene scene, Transition transition) { + + final ViewGroup sceneRoot = scene.getSceneRoot(); + + Transition transitionClone = transition.clone(); + transitionClone.setSceneRoot(sceneRoot); + + Scene oldScene = Scene.getCurrentScene(sceneRoot); + if (oldScene != null && oldScene.isCreatedFromLayoutResource()) { + transitionClone.setCanRemoveViews(true); + } + + sceneChangeSetup(sceneRoot, transitionClone); + + scene.enter(); + + sceneChangeRunTransition(sceneRoot, transitionClone); + } + + private static ArrayMap> getRunningTransitions() { + WeakReference>> runningTransitions = + sRunningTransitions.get(); + if (runningTransitions == null || runningTransitions.get() == null) { + ArrayMap> transitions = + new ArrayMap>(); + runningTransitions = new WeakReference>>( + transitions); + sRunningTransitions.set(runningTransitions); + } + return runningTransitions.get(); + } + + private static void sceneChangeRunTransition(final ViewGroup sceneRoot, + final Transition transition) { + if (transition != null) { + final ViewTreeObserver observer = sceneRoot.getViewTreeObserver(); + final ViewTreeObserver.OnPreDrawListener listener = + new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); + sPendingTransitions.remove(sceneRoot); + // Add to running list, handle end to remove it + final ArrayMap> runningTransitions = + getRunningTransitions(); + ArrayList currentTransitions = runningTransitions.get(sceneRoot); + ArrayList previousRunningTransitions = null; + if (currentTransitions == null) { + currentTransitions = new ArrayList(); + runningTransitions.put(sceneRoot, currentTransitions); + } else if (currentTransitions.size() > 0) { + previousRunningTransitions = new ArrayList(currentTransitions); + } + currentTransitions.add(transition); + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + ArrayList currentTransitions = + runningTransitions.get(sceneRoot); + currentTransitions.remove(transition); + } + }); + transition.captureValues(sceneRoot, false); + if (previousRunningTransitions != null) { + for (Transition runningTransition : previousRunningTransitions) { + runningTransition.resume(); + } + } + transition.playTransition(sceneRoot); + + return true; + } + }; + observer.addOnPreDrawListener(listener); + } + } + + private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) { + + // Capture current values + ArrayList runningTransitions = getRunningTransitions().get(sceneRoot); + + if (runningTransitions != null && runningTransitions.size() > 0) { + for (Transition runningTransition : runningTransitions) { + runningTransition.pause(); + } + } + + if (transition != null) { + transition.captureValues(sceneRoot, true); + } + + // Notify previous scene that it is being exited + Scene previousScene = Scene.getCurrentScene(sceneRoot); + if (previousScene != null) { + previousScene.exit(); + } + } + + /** + * Change to the given scene, using the + * appropriate transition for this particular scene change + * (as specified to the TransitionManager, or the default + * if no such transition exists). + * + * @param scene The Scene to change to + */ + public void transitionTo(Scene scene) { + // Auto transition if there is no transition declared for the Scene, but there is + // a root or parent view + changeScene(scene, getTransition(scene)); + + } + + /** + * Convenience method to simply change to the given scene using + * the default transition for TransitionManager. + * + * @param scene The Scene to change to + */ + public static void go(Scene scene) { + changeScene(scene, sDefaultTransition); + } + + /** + * Convenience method to simply change to the given scene using + * the given transition. + * + *

    Passing in null for the transition parameter will + * result in the scene changing without any transition running, and is + * equivalent to calling {@link Scene#exit()} on the scene root's + * current scene, followed by {@link Scene#enter()} on the scene + * specified by the scene parameter.

    + * + * @param scene The Scene to change to + * @param transition The transition to use for this scene change. A + * value of null causes the scene change to happen with no transition. + */ + public static void go(Scene scene, Transition transition) { + changeScene(scene, transition); + } + + /** + * Convenience method to animate, using the default transition, + * to a new scene defined by all changes within the given scene root between + * calling this method and the next rendering frame. + * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)} + * with a value of null for the transition parameter. + * + * @param sceneRoot The root of the View hierarchy to run the transition on. + */ + public static void beginDelayedTransition(final ViewGroup sceneRoot) { + beginDelayedTransition(sceneRoot, null); + } + + /** + * Convenience method to animate to a new scene defined by all changes within + * the given scene root between calling this method and the next rendering frame. + * Calling this method causes TransitionManager to capture current values in the + * scene root and then post a request to run a transition on the next frame. + * At that time, the new values in the scene root will be captured and changes + * will be animated. There is no need to create a Scene; it is implied by + * changes which take place between calling this method and the next frame when + * the transition begins. + * + *

    Calling this method several times before the next frame (for example, if + * unrelated code also wants to make dynamic changes and run a transition on + * the same scene root), only the first call will trigger capturing values + * and exiting the current scene. Subsequent calls to the method with the + * same scene root during the same frame will be ignored.

    + * + *

    Passing in null for the transition parameter will + * cause the TransitionManager to use its default transition.

    + * + * @param sceneRoot The root of the View hierarchy to run the transition on. + * @param transition The transition to use for this change. A + * value of null causes the TransitionManager to use the default transition. + */ + public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) { + if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) { + if (Transition.DBG) { + Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " + + sceneRoot + ", " + transition); + } + sPendingTransitions.add(sceneRoot); + if (transition == null) { + transition = sDefaultTransition; + } + final Transition transitionClone = transition.clone(); + sceneChangeSetup(sceneRoot, transitionClone); + Scene.setCurrentScene(sceneRoot, null); + sceneChangeRunTransition(sceneRoot, transitionClone); + } + } +} diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java new file mode 100644 index 0000000000000000000000000000000000000000..4545e3b51fc9da906fe1b9117ef14ced3bb163f7 --- /dev/null +++ b/core/java/android/transition/TransitionSet.java @@ -0,0 +1,387 @@ +/* + * 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.transition; + +import android.animation.TimeInterpolator; +import android.util.AndroidRuntimeException; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * A TransitionSet is a parent of child transitions (including other + * TransitionSets). Using TransitionSets enables more complex + * choreography of transitions, where some sets play {@link #ORDERING_TOGETHER} and + * others play {@link #ORDERING_SEQUENTIAL}. For example, {@link AutoTransition} + * uses a TransitionSet to sequentially play a Fade(Fade.OUT), followed by + * a {@link ChangeBounds}, followed by a Fade(Fade.OUT) transition. + * + *

    A TransitionSet can be described in a resource file by using the + * tag transitionSet, along with the standard + * attributes of {@link android.R.styleable#TransitionSet} and + * {@link android.R.styleable#Transition}. Child transitions of the + * TransitionSet object can be loaded by adding those child tags inside the + * enclosing transitionSet tag. For example, the following xml + * describes a TransitionSet that plays a Fade and then a ChangeBounds + * transition on the affected view targets:

    + *
    + *     <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    + *             android:ordering="sequential">
    + *         <fade/>
    + *         <changeBounds/>
    + *     </transitionSet>
    + * 
    + */ +public class TransitionSet extends Transition { + + ArrayList mTransitions = new ArrayList(); + private boolean mPlayTogether = true; + int mCurrentListeners; + boolean mStarted = false; + + /** + * A flag used to indicate that the child transitions of this set + * should all start at the same time. + */ + public static final int ORDERING_TOGETHER = 0; + /** + * A flag used to indicate that the child transitions of this set should + * play in sequence; when one child transition ends, the next child + * transition begins. Note that a transition does not end until all + * instances of it (which are playing on all applicable targets of the + * transition) end. + */ + public static final int ORDERING_SEQUENTIAL = 1; + + /** + * Constructs an empty transition set. Add child transitions to the + * set by calling {@link #addTransition(Transition)} )}. By default, + * child transitions will play {@link #ORDERING_TOGETHER together}. + */ + public TransitionSet() { + } + + /** + * Sets the play order of this set's child transitions. + * + * @param ordering {@link #ORDERING_TOGETHER} to play this set's child + * transitions together, {@link #ORDERING_SEQUENTIAL} to play the child + * transitions in sequence. + * @return This transitionSet object. + */ + public TransitionSet setOrdering(int ordering) { + switch (ordering) { + case ORDERING_SEQUENTIAL: + mPlayTogether = false; + break; + case ORDERING_TOGETHER: + mPlayTogether = true; + break; + default: + throw new AndroidRuntimeException("Invalid parameter for TransitionSet " + + "ordering: " + ordering); + } + return this; + } + + /** + * Returns the ordering of this TransitionSet. By default, the value is + * {@link #ORDERING_TOGETHER}. + * + * @return {@link #ORDERING_TOGETHER} if child transitions will play at the same + * time, {@link #ORDERING_SEQUENTIAL} if they will play in sequence. + * + * @see #setOrdering(int) + */ + public int getOrdering() { + return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL; + } + + /** + * Adds child transition to this set. The order in which this child transition + * is added relative to other child transitions that are added, in addition to + * the {@link #getOrdering() ordering} property, determines the + * order in which the transitions are started. + * + *

    If this transitionSet has a {@link #getDuration() duration} set on it, the + * child transition will inherit that duration. Transitions are assumed to have + * a maximum of one transitionSet parent.

    + * + * @param transition A non-null child transition to be added to this set. + * @return This transitionSet object. + */ + public TransitionSet addTransition(Transition transition) { + if (transition != null) { + mTransitions.add(transition); + transition.mParent = this; + if (mDuration >= 0) { + transition.setDuration(mDuration); + } + } + return this; + } + + /** + * Setting a non-negative duration on a TransitionSet causes all of the child + * transitions (current and future) to inherit this duration. + * + * @param duration The length of the animation, in milliseconds. + * @return This transitionSet object. + */ + @Override + public TransitionSet setDuration(long duration) { + super.setDuration(duration); + if (mDuration >= 0) { + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).setDuration(duration); + } + } + return this; + } + + @Override + public TransitionSet setStartDelay(long startDelay) { + return (TransitionSet) super.setStartDelay(startDelay); + } + + @Override + public TransitionSet setInterpolator(TimeInterpolator interpolator) { + return (TransitionSet) super.setInterpolator(interpolator); + } + + @Override + public TransitionSet addTarget(View target) { + return (TransitionSet) super.addTarget(target); + } + + @Override + public TransitionSet addTarget(int targetId) { + return (TransitionSet) super.addTarget(targetId); + } + + @Override + public TransitionSet addListener(TransitionListener listener) { + return (TransitionSet) super.addListener(listener); + } + + @Override + public TransitionSet removeTarget(int targetId) { + return (TransitionSet) super.removeTarget(targetId); + } + + @Override + public TransitionSet removeTarget(View target) { + return (TransitionSet) super.removeTarget(target); + } + + @Override + public TransitionSet removeListener(TransitionListener listener) { + return (TransitionSet) super.removeListener(listener); + } + + /** + * Removes the specified child transition from this set. + * + * @param transition The transition to be removed. + * @return This transitionSet object. + */ + public TransitionSet removeTransition(Transition transition) { + mTransitions.remove(transition); + transition.mParent = null; + return this; + } + + /** + * Sets up listeners for each of the child transitions. This is used to + * determine when this transition set is finished (all child transitions + * must finish first). + */ + private void setupStartEndListeners() { + TransitionSetListener listener = new TransitionSetListener(this); + for (Transition childTransition : mTransitions) { + childTransition.addListener(listener); + } + mCurrentListeners = mTransitions.size(); + } + + /** + * This listener is used to detect when all child transitions are done, at + * which point this transition set is also done. + */ + static class TransitionSetListener extends TransitionListenerAdapter { + TransitionSet mTransitionSet; + TransitionSetListener(TransitionSet transitionSet) { + mTransitionSet = transitionSet; + } + @Override + public void onTransitionStart(Transition transition) { + if (!mTransitionSet.mStarted) { + mTransitionSet.start(); + mTransitionSet.mStarted = true; + } + } + + @Override + public void onTransitionEnd(Transition transition) { + --mTransitionSet.mCurrentListeners; + if (mTransitionSet.mCurrentListeners == 0) { + // All child trans + mTransitionSet.mStarted = false; + mTransitionSet.end(); + } + transition.removeListener(this); + } + } + + /** + * @hide + */ + @Override + protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, + TransitionValuesMaps endValues) { + for (Transition childTransition : mTransitions) { + childTransition.createAnimators(sceneRoot, startValues, endValues); + } + } + + /** + * @hide + */ + @Override + protected void runAnimators() { + setupStartEndListeners(); + if (!mPlayTogether) { + // Setup sequence with listeners + // TODO: Need to add listeners in such a way that we can remove them later if canceled + for (int i = 1; i < mTransitions.size(); ++i) { + Transition previousTransition = mTransitions.get(i - 1); + final Transition nextTransition = mTransitions.get(i); + previousTransition.addListener(new TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + nextTransition.runAnimators(); + transition.removeListener(this); + } + }); + } + Transition firstTransition = mTransitions.get(0); + if (firstTransition != null) { + firstTransition.runAnimators(); + } + } else { + for (Transition childTransition : mTransitions) { + childTransition.runAnimators(); + } + } + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + int targetId = transitionValues.view.getId(); + if (isValidTarget(transitionValues.view, targetId)) { + for (Transition childTransition : mTransitions) { + if (childTransition.isValidTarget(transitionValues.view, targetId)) { + childTransition.captureStartValues(transitionValues); + } + } + } + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + int targetId = transitionValues.view.getId(); + if (isValidTarget(transitionValues.view, targetId)) { + for (Transition childTransition : mTransitions) { + if (childTransition.isValidTarget(transitionValues.view, targetId)) { + childTransition.captureEndValues(transitionValues); + } + } + } + } + + /** @hide */ + @Override + public void pause() { + super.pause(); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).pause(); + } + } + + /** @hide */ + @Override + public void resume() { + super.resume(); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).resume(); + } + } + + /** @hide */ + @Override + protected void cancel() { + super.cancel(); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).cancel(); + } + } + + @Override + TransitionSet setSceneRoot(ViewGroup sceneRoot) { + super.setSceneRoot(sceneRoot); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).setSceneRoot(sceneRoot); + } + return (TransitionSet) this; + } + + @Override + void setCanRemoveViews(boolean canRemoveViews) { + super.setCanRemoveViews(canRemoveViews); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).setCanRemoveViews(canRemoveViews); + } + } + + @Override + String toString(String indent) { + String result = super.toString(indent); + for (int i = 0; i < mTransitions.size(); ++i) { + result += "\n" + mTransitions.get(i).toString(indent + " "); + } + return result; + } + + @Override + public TransitionSet clone() { + TransitionSet clone = (TransitionSet) super.clone(); + clone.mTransitions = new ArrayList(); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + clone.addTransition((Transition) mTransitions.get(i).clone()); + } + return clone; + } + +} diff --git a/core/java/android/transition/TransitionValues.java b/core/java/android/transition/TransitionValues.java new file mode 100644 index 0000000000000000000000000000000000000000..8989f89295f6762e2fc68a6b2af068ad1a1c6f8e --- /dev/null +++ b/core/java/android/transition/TransitionValues.java @@ -0,0 +1,82 @@ +/* + * 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.transition; + +import android.util.ArrayMap; +import android.view.View; +import android.view.ViewGroup; + +import java.util.Map; + +/** + * Data structure which holds cached values for the transition. + * The view field is the target which all of the values pertain to. + * The values field is a map which holds information for fields + * according to names selected by the transitions. These names should + * be unique to avoid clobbering values stored by other transitions, + * such as the convention project:transition_name:property_name. For + * example, the platform might store a property "alpha" in a transition + * "Fader" as "android:fader:alpha". + * + *

    These values are cached during the + * {@link Transition#captureStartValues(TransitionValues)} + * capture} phases of a scene change, once when the start values are captured + * and again when the end values are captured. These start/end values are then + * passed into the transitions via the + * for {@link Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)} + * method.

    + */ +public class TransitionValues { + + /** + * The View with these values + */ + public View view; + + /** + * The set of values tracked by transitions for this scene + */ + public final Map values = new ArrayMap(); + + @Override + public boolean equals(Object other) { + if (other instanceof TransitionValues) { + if (view == ((TransitionValues) other).view) { + if (values.equals(((TransitionValues) other).values)) { + return true; + } + } + } + return false; + } + + @Override + public int hashCode() { + return 31*view.hashCode() + values.hashCode(); + } + + @Override + public String toString() { + String returnValue = "TransitionValues@" + Integer.toHexString(hashCode()) + ":\n"; + returnValue += " view = " + view + "\n"; + returnValue += " values:"; + for (String s : values.keySet()) { + returnValue += " " + s + ": " + values.get(s) + "\n"; + } + return returnValue; + } +} \ No newline at end of file diff --git a/core/java/android/transition/TransitionValuesMaps.java b/core/java/android/transition/TransitionValuesMaps.java new file mode 100644 index 0000000000000000000000000000000000000000..131596b97a5a4d6d172391f533f18ea301200779 --- /dev/null +++ b/core/java/android/transition/TransitionValuesMaps.java @@ -0,0 +1,30 @@ +/* + * 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.transition; + +import android.util.ArrayMap; +import android.util.LongSparseArray; +import android.util.SparseArray; +import android.view.View; + +class TransitionValuesMaps { + ArrayMap viewValues = + new ArrayMap(); + SparseArray idValues = new SparseArray(); + LongSparseArray itemIdValues = + new LongSparseArray(); +} diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java new file mode 100644 index 0000000000000000000000000000000000000000..44f92cd7ed4a72770bae44cf38a01686412f3375 --- /dev/null +++ b/core/java/android/transition/Visibility.java @@ -0,0 +1,223 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.view.View; +import android.view.ViewGroup; + +/** + * This transition tracks changes to the visibility of target views in the + * start and end scenes. Visibility is determined not just by the + * {@link View#setVisibility(int)} state of views, but also whether + * 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 both of the methods + * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}, + * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}, + */ +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 final String[] sTransitionProperties = { + PROPNAME_VISIBILITY, + PROPNAME_PARENT, + }; + + private static class VisibilityInfo { + boolean visibilityChange; + boolean fadeIn; + int startVisibility; + int endVisibility; + ViewGroup startParent; + ViewGroup endParent; + } + + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + private void captureValues(TransitionValues transitionValues) { + int visibility = transitionValues.view.getVisibility(); + transitionValues.values.put(PROPNAME_VISIBILITY, visibility); + transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent()); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + /** + * Returns whether the view is 'visible' according to the given values + * object. This is determined by testing the same properties in the values + * object that are used to determine whether the object is appearing or + * disappearing in the {@link + * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)} + * method. This method can be called by, for example, subclasses that want + * to know whether the object is visible in the same way that Visibility + * determines it for the actual animation. + * + * @param values The TransitionValues object that holds the information by + * which visibility is determined. + * @return True if the view reference by values is visible, + * false otherwise. + */ + public boolean isVisible(TransitionValues values) { + if (values == null) { + return false; + } + int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY); + View parent = (View) values.values.get(PROPNAME_PARENT); + + return visibility == View.VISIBLE && parent != null; + } + + private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues, + TransitionValues endValues) { + final VisibilityInfo visInfo = new VisibilityInfo(); + visInfo.visibilityChange = false; + visInfo.fadeIn = false; + if (startValues != null) { + visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); + 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 = (ViewGroup) endValues.values.get(PROPNAME_PARENT); + } else { + visInfo.endVisibility = -1; + visInfo.endParent = null; + } + if (startValues != null && endValues != null) { + if (visInfo.startVisibility == visInfo.endVisibility && + visInfo.startParent == visInfo.endParent) { + return visInfo; + } else { + if (visInfo.startVisibility != visInfo.endVisibility) { + if (visInfo.startVisibility == View.VISIBLE) { + visInfo.fadeIn = false; + visInfo.visibilityChange = true; + } else if (visInfo.endVisibility == View.VISIBLE) { + visInfo.fadeIn = true; + visInfo.visibilityChange = true; + } + // no visibilityChange if going between INVISIBLE and GONE + } else if (visInfo.startParent != visInfo.endParent) { + if (visInfo.endParent == null) { + visInfo.fadeIn = false; + visInfo.visibilityChange = true; + } else if (visInfo.startParent == null) { + visInfo.fadeIn = true; + visInfo.visibilityChange = true; + } + } + } + } + if (startValues == null) { + visInfo.fadeIn = true; + visInfo.visibilityChange = true; + } else if (endValues == null) { + visInfo.fadeIn = false; + visInfo.visibilityChange = true; + } + return visInfo; + } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues); + if (visInfo.visibilityChange) { + // Only transition views that are either targets of this transition + // or whose parent hierarchies remain stable between scenes + boolean isTarget = false; + if (mTargets.size() > 0 || mTargetIds.size() > 0) { + 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))) { + if (visInfo.fadeIn) { + return onAppear(sceneRoot, startValues, visInfo.startVisibility, + endValues, visInfo.endVisibility); + } else { + return onDisappear(sceneRoot, startValues, visInfo.startVisibility, + endValues, visInfo.endVisibility + ); + } + } + } + return null; + } + + /** + * The default implementation of this method does nothing. Subclasses + * should override if they need to create an Animator when targets appear. + * The method should only be called by the Visibility class; it is + * not intended to be called from external classes. + * + * @param sceneRoot The root of the transition hierarchy + * @param startValues The target values in the start scene + * @param startVisibility The target visibility in the start scene + * @param endValues The target values in the end scene + * @param endVisibility The target visibility in the end scene + * @return An Animator to be started at the appropriate time in the + * overall transition for this scene change. A null value means no animation + * should be run. + */ + public Animator onAppear(ViewGroup sceneRoot, + TransitionValues startValues, int startVisibility, + TransitionValues endValues, int endVisibility) { + return null; + } + + /** + * The default implementation of this method does nothing. Subclasses + * should override if they need to create an Animator when targets disappear. + * The method should only be called by the Visibility class; it is + * not intended to be called from external classes. + * + * + * @param sceneRoot The root of the transition hierarchy + * @param startValues The target values in the start scene + * @param startVisibility The target visibility in the start scene + * @param endValues The target values in the end scene + * @param endVisibility The target visibility in the end scene + * @return An Animator to be started at the appropriate time in the + * overall transition for this scene change. A null value means no animation + * should be run. + */ + public Animator onDisappear(ViewGroup sceneRoot, + TransitionValues startValues, int startVisibility, + TransitionValues endValues, int endVisibility) { + return null; + } +} diff --git a/core/java/android/transition/package.html b/core/java/android/transition/package.html new file mode 100644 index 0000000000000000000000000000000000000000..f357d3457a46bd4ed47e6583a26a084ee368490d --- /dev/null +++ b/core/java/android/transition/package.html @@ -0,0 +1,26 @@ + + +

    The classes in this package enable "scenes & transitions" functionality for +view hiearchies.

    + +

    A Scene is an encapsulation of the state of a view hierarchy, +including the views in that hierarchy and the various values (layout-related +and otherwise) that those views have. A scene can be defined by a layout hierarchy +directly or by code which sets up the scene dynamically as it is entered.

    + +

    A Transition is a mechanism to automatically animate changes that occur +when a new scene is entered. Some transition capabilities are automatic. That +is, entering a scene may cause animations to run which fade out views that +go away, changeBounds and resize existing views that change, and fade in views that +become visible. There are additional transitions that can animate other +attributes, such as color changes, and which can optionally be specified +to take place during particular scene changes. Finally, developers can +define their own Transition subclasses which monitor particular property +changes and which run custom animations when those properties change values.

    + +

    TransitionManager is used to specify custom transitions for particular +scene changes, and to cause scene changes with specific transitions to +take place.

    + + + diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java new file mode 100644 index 0000000000000000000000000000000000000000..df1d4cd7047a62922eb3a77c260b0ccfde315e80 --- /dev/null +++ b/core/java/android/util/ArrayMap.java @@ -0,0 +1,829 @@ +/* + * 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.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * ArrayMap is a generic key->value mapping data structure that is + * designed to be more memory efficient than a traditional {@link java.util.HashMap}. + * It keeps its mappings in an array data structure -- an integer array of hash + * codes for each item, and an Object array of the key/value pairs. This allows it to + * avoid having to create an extra object for every entry put in to the map, and it + * also tries to control the growth of the size of these arrays more aggressively + * (since growing them only requires copying the entries in the array, not rebuilding + * a hash map). + * + *

    Note that this implementation is not intended to be appropriate for data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    Because this container is intended to better balance memory use, unlike most other + * standard Java containers it will shrink its array as items are removed from it. Currently + * you have no control over this shrinking -- if you set a capacity and then remove an + * item, it may reduce the capacity to better match the current size. In the future an + * explicit call to set the capacity should turn off this aggressive shrinking behavior.

    + */ +public final class ArrayMap implements Map { + private static final boolean DEBUG = false; + private static final String TAG = "ArrayMap"; + + /** + * The minimum amount by which the capacity of a ArrayMap will increase. + * This is tuned to be relatively space-efficient. + */ + private static final int BASE_SIZE = 4; + + /** + * Maximum number of entries to have in array caches. + */ + private static final int CACHE_SIZE = 10; + + /** + * @hide Special immutable empty ArrayMap. + */ + public static final ArrayMap EMPTY = new ArrayMap(true); + + /** + * Caches of small array objects to avoid spamming garbage. The cache + * Object[] variable is a pointer to a linked list of array objects. + * The first entry in the array is a pointer to the next array in the + * list; the second entry is a pointer to the int[] hash code array for it. + */ + static Object[] mBaseCache; + static int mBaseCacheSize; + static Object[] mTwiceBaseCache; + static int mTwiceBaseCacheSize; + + /** + * Special hash array value that indicates the container is immutable. + */ + static final int[] EMPTY_IMMUTABLE_INTS = new int[0]; + + int[] mHashes; + Object[] mArray; + int mSize; + MapCollections mCollections; + + int indexOf(Object key, int hash) { + final int N = mSize; + + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + + int index = ContainerHelpers.binarySearch(mHashes, N, hash); + + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + + // If the key at the returned index matches, that's what we want. + if (key.equals(mArray[index<<1])) { + return index; + } + + // Search for a matching key after the index. + int end; + for (end = index + 1; end < N && mHashes[end] == hash; end++) { + if (key.equals(mArray[end << 1])) return end; + } + + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { + if (key.equals(mArray[i << 1])) return i; + } + + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; + } + + int indexOfNull() { + final int N = mSize; + + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + + int index = ContainerHelpers.binarySearch(mHashes, N, 0); + + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + + // If the key at the returned index matches, that's what we want. + if (null == mArray[index<<1]) { + return index; + } + + // Search for a matching key after the index. + int end; + for (end = index + 1; end < N && mHashes[end] == 0; end++) { + if (null == mArray[end << 1]) return end; + } + + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) { + if (null == mArray[i << 1]) return i; + } + + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; + } + + private void allocArrays(final int size) { + if (mHashes == EMPTY_IMMUTABLE_INTS) { + throw new UnsupportedOperationException("ArrayMap is immutable"); + } + if (size == (BASE_SIZE*2)) { + synchronized (ArrayMap.class) { + if (mTwiceBaseCache != null) { + final Object[] array = mTwiceBaseCache; + mArray = array; + mTwiceBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mTwiceBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes + + " now have " + mTwiceBaseCacheSize + " entries"); + return; + } + } + } else if (size == BASE_SIZE) { + synchronized (ArrayMap.class) { + if (mBaseCache != null) { + final Object[] array = mBaseCache; + mArray = array; + mBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes + + " now have " + mBaseCacheSize + " entries"); + return; + } + } + } + + mHashes = new int[size]; + mArray = new Object[size<<1]; + } + + private static void freeArrays(final int[] hashes, final Object[] array, final int size) { + if (hashes.length == (BASE_SIZE*2)) { + synchronized (ArrayMap.class) { + if (mTwiceBaseCacheSize < CACHE_SIZE) { + array[0] = mTwiceBaseCache; + array[1] = hashes; + for (int i=(size<<1)-1; i>=2; i--) { + array[i] = null; + } + mTwiceBaseCache = array; + mTwiceBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 2x cache " + array + + " now have " + mTwiceBaseCacheSize + " entries"); + } + } + } else if (hashes.length == BASE_SIZE) { + synchronized (ArrayMap.class) { + if (mBaseCacheSize < CACHE_SIZE) { + array[0] = mBaseCache; + array[1] = hashes; + for (int i=(size<<1)-1; i>=2; i--) { + array[i] = null; + } + mBaseCache = array; + mBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 1x cache " + array + + " now have " + mBaseCacheSize + " entries"); + } + } + } + } + + /** + * Create a new empty ArrayMap. The default capacity of an array map is 0, and + * will grow once items are added to it. + */ + public ArrayMap() { + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + mSize = 0; + } + + /** + * Create a new ArrayMap with a given initial capacity. + */ + public ArrayMap(int capacity) { + if (capacity == 0) { + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + } else { + allocArrays(capacity); + } + mSize = 0; + } + + private ArrayMap(boolean immutable) { + mHashes = EMPTY_IMMUTABLE_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + mSize = 0; + } + + /** + * Create a new ArrayMap with the mappings from the given ArrayMap. + */ + public ArrayMap(ArrayMap map) { + this(); + if (map != null) { + putAll(map); + } + } + + /** + * Make the array map empty. All storage is released. + */ + @Override + public void clear() { + if (mSize > 0) { + freeArrays(mHashes, mArray, mSize); + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + mSize = 0; + } + } + + /** + * @hide + * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap. + */ + public void erase() { + if (mSize > 0) { + final int N = mSize<<1; + final Object[] array = mArray; + for (int i=0; iminimumCapacity + * items. + */ + public void ensureCapacity(int minimumCapacity) { + if (mHashes.length < minimumCapacity) { + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(minimumCapacity); + if (mSize > 0) { + System.arraycopy(ohashes, 0, mHashes, 0, mSize); + System.arraycopy(oarray, 0, mArray, 0, mSize<<1); + } + freeArrays(ohashes, oarray, mSize); + } + } + + /** + * Check whether a key exists in the array. + * + * @param key The key to search for. + * @return Returns true if the key exists, else false. + */ + @Override + public boolean containsKey(Object key) { + return key == null ? (indexOfNull() >= 0) : (indexOf(key, key.hashCode()) >= 0); + } + + int indexOfValue(Object value) { + final int N = mSize*2; + final Object[] array = mArray; + if (value == null) { + for (int i=1; i>1; + } + } + } else { + for (int i=1; i>1; + } + } + } + return -1; + } + + /** + * Check whether a value exists in the array. This requires a linear search + * through the entire array. + * + * @param value The value to search for. + * @return Returns true if the value exists, else false. + */ + @Override + public boolean containsValue(Object value) { + return indexOfValue(value) >= 0; + } + + /** + * Retrieve a value from the array. + * @param key The key of the value to retrieve. + * @return Returns the value associated with the given key, + * or null if there is no such key. + */ + @Override + public V get(Object key) { + final int index = key == null ? indexOfNull() : indexOf(key, key.hashCode()); + return index >= 0 ? (V)mArray[(index<<1)+1] : null; + } + + /** + * Return the key at the given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the key stored at the given index. + */ + public K keyAt(int index) { + return (K)mArray[index << 1]; + } + + /** + * Return the value at the given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value stored at the given index. + */ + public V valueAt(int index) { + return (V)mArray[(index << 1) + 1]; + } + + /** + * Set the value at a given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @param value The new value to store at this index. + * @return Returns the previous value at the given index. + */ + public V setValueAt(int index, V value) { + index = (index << 1) + 1; + V old = (V)mArray[index]; + mArray[index] = value; + return old; + } + + /** + * Return true if the array map contains no items. + */ + @Override + public boolean isEmpty() { + return mSize <= 0; + } + + /** + * Add a new value to the array map. + * @param key The key under which to store the value. Must not be null. If + * this key already exists in the array, its value will be replaced. + * @param value The value to store for the given key. + * @return Returns the old value that was stored for the given key, or null if there + * was no such key. + */ + @Override + public V put(K key, V value) { + final int hash; + int index; + if (key == null) { + hash = 0; + index = indexOfNull(); + } else { + hash = key.hashCode(); + index = indexOf(key, hash); + } + if (index >= 0) { + index = (index<<1) + 1; + final V old = (V)mArray[index]; + mArray[index] = value; + return old; + } + + index = ~index; + if (mSize >= mHashes.length) { + final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) + : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); + + if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n); + + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + + if (mHashes.length > 0) { + if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); + System.arraycopy(oarray, 0, mArray, 0, oarray.length); + } + + freeArrays(ohashes, oarray, mSize); + } + + if (index < mSize) { + if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index) + + " to " + (index+1)); + System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); + System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1); + } + + mHashes[index] = hash; + mArray[index<<1] = key; + mArray[(index<<1)+1] = value; + mSize++; + return null; + } + + /** + * Special fast path for appending items to the end of the array without validation. + * The array must already be large enough to contain the item. + * @hide + */ + public void append(K key, V value) { + int index = mSize; + final int hash = key == null ? 0 : key.hashCode(); + if (index >= mHashes.length) { + throw new IllegalStateException("Array is full"); + } + if (index > 0 && mHashes[index-1] > hash) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "New hash " + hash + + " is before end of array hash " + mHashes[index-1] + + " at index " + index + " key " + key, e); + put(key, value); + return; + } + mSize = index+1; + mHashes[index] = hash; + index <<= 1; + mArray[index] = key; + mArray[index+1] = value; + } + + /** + * Perform a {@link #put(Object, Object)} of all key/value pairs in array + * @param array The array whose contents are to be retrieved. + */ + public void putAll(ArrayMap array) { + final int N = array.mSize; + ensureCapacity(mSize + N); + if (mSize == 0) { + if (N > 0) { + System.arraycopy(array.mHashes, 0, mHashes, 0, N); + System.arraycopy(array.mArray, 0, mArray, 0, N<<1); + mSize = N; + } + } else { + for (int i=0; i= 0) { + return removeAt(index); + } + + return null; + } + + /** + * Remove the key/value mapping at the given index. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value that was stored at this index. + */ + public V removeAt(int index) { + final Object old = mArray[(index << 1) + 1]; + if (mSize <= 1) { + // Now empty. + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); + freeArrays(mHashes, mArray, mSize); + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + mSize = 0; + } else { + if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) { + // Shrunk enough to reduce size of arrays. We don't allow it to + // shrink smaller than (BASE_SIZE*2) to avoid flapping between + // that and BASE_SIZE. + final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2); + + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); + + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + + mSize--; + if (index > 0) { + if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, index); + System.arraycopy(oarray, 0, mArray, 0, index << 1); + } + if (index < mSize) { + if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize + + " to " + index); + System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index); + System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1, + (mSize - index) << 1); + } + } else { + mSize--; + if (index < mSize) { + if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize + + " to " + index); + System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index); + System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, + (mSize - index) << 1); + } + mArray[mSize << 1] = null; + mArray[(mSize << 1) + 1] = null; + } + } + return (V)old; + } + + /** + * Return the number of items in this array map. + */ + @Override + public int size() { + return mSize; + } + + /** + * {@inheritDoc} + * + *

    This implementation returns false if the object is not a map, or + * if the maps have different sizes. Otherwise, for each key in this map, + * values of both maps are compared. If the values for any key are not + * equal, the method returns false, otherwise it returns true. + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Map) { + Map map = (Map) object; + if (size() != map.size()) { + return false; + } + + try { + for (int i=0; iThis implementation composes a string by iterating over its mappings. If + * this map contains itself as a key or a value, the string "(this Map)" + * will appear in its place. + */ + @Override + public String toString() { + if (isEmpty()) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + Object key = keyAt(i); + if (key != this) { + buffer.append(key); + } else { + buffer.append("(this Map)"); + } + buffer.append('='); + Object value = valueAt(i); + if (value != this) { + buffer.append(value); + } else { + buffer.append("(this Map)"); + } + } + buffer.append('}'); + return buffer.toString(); + } + + // ------------------------------------------------------------------------ + // Interop with traditional Java containers. Not as efficient as using + // specialized collection APIs. + // ------------------------------------------------------------------------ + + private MapCollections getCollection() { + if (mCollections == null) { + mCollections = new MapCollections() { + @Override + protected int colGetSize() { + return mSize; + } + + @Override + protected Object colGetEntry(int index, int offset) { + return mArray[(index<<1) + offset]; + } + + @Override + protected int colIndexOfKey(Object key) { + return key == null ? indexOfNull() : indexOf(key, key.hashCode()); + } + + @Override + protected int colIndexOfValue(Object value) { + return indexOfValue(value); + } + + @Override + protected Map colGetMap() { + return ArrayMap.this; + } + + @Override + protected void colPut(K key, V value) { + put(key, value); + } + + @Override + protected V colSetValue(int index, V value) { + return setValueAt(index, value); + } + + @Override + protected void colRemoveAt(int index) { + removeAt(index); + } + + @Override + protected void colClear() { + clear(); + } + }; + } + return mCollections; + } + + /** + * Determine if the array map contains all of the keys in the given collection. + * @param collection The collection whose contents are to be checked against. + * @return Returns true if this array map contains a key for every entry + * in collection, else returns false. + */ + public boolean containsAll(Collection collection) { + return MapCollections.containsAllHelper(this, collection); + } + + /** + * Perform a {@link #put(Object, Object)} of all key/value pairs in map + * @param map The map whose contents are to be retrieved. + */ + @Override + public void putAll(Map map) { + ensureCapacity(mSize + map.size()); + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Remove all keys in the array map that exist in the given collection. + * @param collection The collection whose contents are to be used to remove keys. + * @return Returns true if any keys were removed from the array map, else false. + */ + public boolean removeAll(Collection collection) { + return MapCollections.removeAllHelper(this, collection); + } + + /** + * Remove all keys in the array map that do not exist in the given collection. + * @param collection The collection whose contents are to be used to determine which + * keys to keep. + * @return Returns true if any keys were removed from the array map, else false. + */ + public boolean retainAll(Collection collection) { + return MapCollections.retainAllHelper(this, collection); + } + + /** + * Return a {@link java.util.Set} for iterating over and interacting with all mappings + * in the array map. + * + *

    Note: this is a very inefficient way to access the array contents, it + * requires generating a number of temporary objects.

    + * + *

    Note:

    the semantics of this + * Set are subtly different than that of a {@link java.util.HashMap}: most important, + * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single + * object that exists for the entire iterator, so you can not hold on to it + * after calling {@link java.util.Iterator#next() Iterator.next}.

    + */ + @Override + public Set> entrySet() { + return getCollection().getEntrySet(); + } + + /** + * Return a {@link java.util.Set} for iterating over and interacting with all keys + * in the array map. + * + *

    Note: this is a fairly inefficient way to access the array contents, it + * requires generating a number of temporary objects.

    + */ + @Override + public Set keySet() { + return getCollection().getKeySet(); + } + + /** + * Return a {@link java.util.Collection} for iterating over and interacting with all values + * in the array map. + * + *

    Note: this is a fairly inefficient way to access the array contents, it + * requires generating a number of temporary objects.

    + */ + @Override + public Collection values() { + return getCollection().getValues(); + } +} diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java new file mode 100644 index 0000000000000000000000000000000000000000..3c695e906647936fa0fdd88a5bf468d6937803d9 --- /dev/null +++ b/core/java/android/util/ArraySet.java @@ -0,0 +1,670 @@ +/* + * 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.util; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * ArraySet is a generic set data structure that is designed to be more memory efficient than a + * traditional {@link java.util.HashSet}. The design is very similar to + * {@link ArrayMap}, with all of the caveats described there. This implementation is + * separate from ArrayMap, however, so the Object array contains only one item for each + * entry in the set (instead of a pair for a mapping). + * + *

    Note that this implementation is not intended to be appropriate for data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashSet, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    Because this container is intended to better balance memory use, unlike most other + * standard Java containers it will shrink its array as items are removed from it. Currently + * you have no control over this shrinking -- if you set a capacity and then remove an + * item, it may reduce the capacity to better match the current size. In the future an + * explicit call to set the capacity should turn off this aggressive shrinking behavior.

    + * + * @hide + */ +public final class ArraySet implements Collection, Set { + private static final boolean DEBUG = false; + private static final String TAG = "ArraySet"; + + /** + * The minimum amount by which the capacity of a ArraySet will increase. + * This is tuned to be relatively space-efficient. + */ + private static final int BASE_SIZE = 4; + + /** + * Maximum number of entries to have in array caches. + */ + private static final int CACHE_SIZE = 10; + + /** + * Caches of small array objects to avoid spamming garbage. The cache + * Object[] variable is a pointer to a linked list of array objects. + * The first entry in the array is a pointer to the next array in the + * list; the second entry is a pointer to the int[] hash code array for it. + */ + static Object[] mBaseCache; + static int mBaseCacheSize; + static Object[] mTwiceBaseCache; + static int mTwiceBaseCacheSize; + + int[] mHashes; + Object[] mArray; + int mSize; + MapCollections mCollections; + + private int indexOf(Object key, int hash) { + final int N = mSize; + + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + + int index = ContainerHelpers.binarySearch(mHashes, N, hash); + + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + + // If the key at the returned index matches, that's what we want. + if (key.equals(mArray[index])) { + return index; + } + + // Search for a matching key after the index. + int end; + for (end = index + 1; end < N && mHashes[end] == hash; end++) { + if (key.equals(mArray[end])) return end; + } + + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { + if (key.equals(mArray[i])) return i; + } + + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; + } + + private int indexOfNull() { + final int N = mSize; + + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + + int index = ContainerHelpers.binarySearch(mHashes, N, 0); + + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + + // If the key at the returned index matches, that's what we want. + if (null == mArray[index]) { + return index; + } + + // Search for a matching key after the index. + int end; + for (end = index + 1; end < N && mHashes[end] == 0; end++) { + if (null == mArray[end]) return end; + } + + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) { + if (null == mArray[i]) return i; + } + + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; + } + + private void allocArrays(final int size) { + if (size == (BASE_SIZE*2)) { + synchronized (ArraySet.class) { + if (mTwiceBaseCache != null) { + final Object[] array = mTwiceBaseCache; + mArray = array; + mTwiceBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mTwiceBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes + + " now have " + mTwiceBaseCacheSize + " entries"); + return; + } + } + } else if (size == BASE_SIZE) { + synchronized (ArraySet.class) { + if (mBaseCache != null) { + final Object[] array = mBaseCache; + mArray = array; + mBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes + + " now have " + mBaseCacheSize + " entries"); + return; + } + } + } + + mHashes = new int[size]; + mArray = new Object[size]; + } + + private static void freeArrays(final int[] hashes, final Object[] array, final int size) { + if (hashes.length == (BASE_SIZE*2)) { + synchronized (ArraySet.class) { + if (mTwiceBaseCacheSize < CACHE_SIZE) { + array[0] = mTwiceBaseCache; + array[1] = hashes; + for (int i=size-1; i>=2; i--) { + array[i] = null; + } + mTwiceBaseCache = array; + mTwiceBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 2x cache " + array + + " now have " + mTwiceBaseCacheSize + " entries"); + } + } + } else if (hashes.length == BASE_SIZE) { + synchronized (ArraySet.class) { + if (mBaseCacheSize < CACHE_SIZE) { + array[0] = mBaseCache; + array[1] = hashes; + for (int i=size-1; i>=2; i--) { + array[i] = null; + } + mBaseCache = array; + mBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 1x cache " + array + + " now have " + mBaseCacheSize + " entries"); + } + } + } + } + + /** + * Create a new empty ArraySet. The default capacity of an array map is 0, and + * will grow once items are added to it. + */ + public ArraySet() { + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + mSize = 0; + } + + /** + * Create a new ArraySet with a given initial capacity. + */ + public ArraySet(int capacity) { + if (capacity == 0) { + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + } else { + allocArrays(capacity); + } + mSize = 0; + } + + /** + * Create a new ArraySet with the mappings from the given ArraySet. + */ + public ArraySet(ArraySet set) { + this(); + if (set != null) { + addAll(set); + } + } + + + /** + * Make the array map empty. All storage is released. + */ + @Override + public void clear() { + if (mSize != 0) { + freeArrays(mHashes, mArray, mSize); + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + mSize = 0; + } + } + + /** + * Ensure the array map can hold at least minimumCapacity + * items. + */ + public void ensureCapacity(int minimumCapacity) { + if (mHashes.length < minimumCapacity) { + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(minimumCapacity); + if (mSize > 0) { + System.arraycopy(ohashes, 0, mHashes, 0, mSize); + System.arraycopy(oarray, 0, mArray, 0, mSize); + } + freeArrays(ohashes, oarray, mSize); + } + } + + /** + * Check whether a value exists in the set. + * + * @param key The value to search for. + * @return Returns true if the value exists, else false. + */ + @Override + public boolean contains(Object key) { + return key == null ? (indexOfNull() >= 0) : (indexOf(key, key.hashCode()) >= 0); + } + + /** + * Return the value at the given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value stored at the given index. + */ + public E valueAt(int index) { + return (E)mArray[index]; + } + + /** + * Return true if the array map contains no items. + */ + @Override + public boolean isEmpty() { + return mSize <= 0; + } + + /** + * Adds the specified object to this set. The set is not modified if it + * already contains the object. + * + * @param value the object to add. + * @return {@code true} if this set is modified, {@code false} otherwise. + * @throws ClassCastException + * when the class of the object is inappropriate for this set. + */ + @Override + public boolean add(E value) { + final int hash; + int index; + if (value == null) { + hash = 0; + index = indexOfNull(); + } else { + hash = value.hashCode(); + index = indexOf(value, hash); + } + if (index >= 0) { + return false; + } + + index = ~index; + if (mSize >= mHashes.length) { + final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) + : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); + + if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n); + + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + + if (mHashes.length > 0) { + if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); + System.arraycopy(oarray, 0, mArray, 0, oarray.length); + } + + freeArrays(ohashes, oarray, mSize); + } + + if (index < mSize) { + if (DEBUG) Log.d(TAG, "add: move " + index + "-" + (mSize-index) + + " to " + (index+1)); + System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); + System.arraycopy(mArray, index, mArray, index + 1, mSize - index); + } + + mHashes[index] = hash; + mArray[index] = value; + mSize++; + return true; + } + + /** + * Perform a {@link #add(Object)} of all values in array + * @param array The array whose contents are to be retrieved. + */ + public void putAll(ArraySet array) { + final int N = array.mSize; + ensureCapacity(mSize + N); + if (mSize == 0) { + if (N > 0) { + System.arraycopy(array.mHashes, 0, mHashes, 0, N); + System.arraycopy(array.mArray, 0, mArray, 0, N); + mSize = N; + } + } else { + for (int i=0; i= 0) { + removeAt(index); + return true; + } + return false; + } + + /** + * Remove the key/value mapping at the given index. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value that was stored at this index. + */ + public E removeAt(int index) { + final Object old = mArray[index]; + if (mSize <= 1) { + // Now empty. + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); + freeArrays(mHashes, mArray, mSize); + mHashes = ContainerHelpers.EMPTY_INTS; + mArray = ContainerHelpers.EMPTY_OBJECTS; + mSize = 0; + } else { + if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) { + // Shrunk enough to reduce size of arrays. We don't allow it to + // shrink smaller than (BASE_SIZE*2) to avoid flapping between + // that and BASE_SIZE. + final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2); + + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); + + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + + mSize--; + if (index > 0) { + if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, index); + System.arraycopy(oarray, 0, mArray, 0, index); + } + if (index < mSize) { + if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize + + " to " + index); + System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index); + System.arraycopy(oarray, index + 1, mArray, index, mSize - index); + } + } else { + mSize--; + if (index < mSize) { + if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize + + " to " + index); + System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index); + System.arraycopy(mArray, index + 1, mArray, index, mSize - index); + } + mArray[mSize] = null; + } + } + return (E)old; + } + + /** + * Return the number of items in this array map. + */ + @Override + public int size() { + return mSize; + } + + @Override + public Object[] toArray() { + Object[] result = new Object[mSize]; + System.arraycopy(mArray, 0, result, 0, mSize); + return result; + } + + @Override + public T[] toArray(T[] array) { + if (array.length < mSize) { + @SuppressWarnings("unchecked") T[] newArray + = (T[]) Array.newInstance(array.getClass().getComponentType(), mSize); + array = newArray; + } + System.arraycopy(mArray, 0, array, 0, mSize); + if (array.length > mSize) { + array[mSize] = null; + } + return array; + } + + /** + * {@inheritDoc} + * + *

    This implementation returns false if the object is not a set, or + * if the sets have different sizes. Otherwise, for each value in this + * set, it checks to make sure the value also exists in the other set. + * If any value doesn't exist, the method returns false; otherwise, it + * returns true. + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Set) { + Set set = (Set) object; + if (size() != set.size()) { + return false; + } + + try { + for (int i=0; iThis implementation composes a string by iterating over its values. If + * this set contains itself as a value, the string "(this Set)" + * will appear in its place. + */ + @Override + public String toString() { + if (isEmpty()) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 14); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + Object value = valueAt(i); + if (value != this) { + buffer.append(value); + } else { + buffer.append("(this Set)"); + } + } + buffer.append('}'); + return buffer.toString(); + } + + // ------------------------------------------------------------------------ + // Interop with traditional Java containers. Not as efficient as using + // specialized collection APIs. + // ------------------------------------------------------------------------ + + private MapCollections getCollection() { + if (mCollections == null) { + mCollections = new MapCollections() { + @Override + protected int colGetSize() { + return mSize; + } + + @Override + protected Object colGetEntry(int index, int offset) { + return mArray[index]; + } + + @Override + protected int colIndexOfKey(Object key) { + return key == null ? indexOfNull() : indexOf(key, key.hashCode()); + } + + @Override + protected int colIndexOfValue(Object value) { + return value == null ? indexOfNull() : indexOf(value, value.hashCode()); + } + + @Override + protected Map colGetMap() { + throw new UnsupportedOperationException("not a map"); + } + + @Override + protected void colPut(E key, E value) { + add(key); + } + + @Override + protected E colSetValue(int index, E value) { + throw new UnsupportedOperationException("not a map"); + } + + @Override + protected void colRemoveAt(int index) { + removeAt(index); + } + + @Override + protected void colClear() { + clear(); + } + }; + } + return mCollections; + } + + @Override + public Iterator iterator() { + return getCollection().getKeySet().iterator(); + } + + @Override + public boolean containsAll(Collection collection) { + Iterator it = collection.iterator(); + while (it.hasNext()) { + if (!contains(it.next())) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection collection) { + ensureCapacity(mSize + collection.size()); + boolean added = false; + for (E value : collection) { + added |= add(value); + } + return added; + } + + @Override + public boolean removeAll(Collection collection) { + boolean removed = false; + for (Object value : collection) { + removed |= remove(value); + } + return removed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean removed = false; + for (int i=mSize-1; i>=0; i--) { + if (!collection.contains(mArray[i])) { + removeAt(i); + removed = true; + } + } + return removed; + } +} diff --git a/core/java/android/util/ContainerHelpers.java b/core/java/android/util/ContainerHelpers.java new file mode 100644 index 0000000000000000000000000000000000000000..624c4bdfda157ab62a6dcee874f301d7ebfb5b32 --- /dev/null +++ b/core/java/android/util/ContainerHelpers.java @@ -0,0 +1,63 @@ +/* + * 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.util; + +class ContainerHelpers { + static final boolean[] EMPTY_BOOLEANS = new boolean[0]; + static final int[] EMPTY_INTS = new int[0]; + static final long[] EMPTY_LONGS = new long[0]; + static final Object[] EMPTY_OBJECTS = new Object[0]; + + // This is Arrays.binarySearch(), but doesn't do any argument validation. + static int binarySearch(int[] array, int size, int value) { + int lo = 0; + int hi = size - 1; + + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final int midVal = array[mid]; + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } + + static int binarySearch(long[] array, int size, long value) { + int lo = 0; + int hi = size - 1; + + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final long midVal = array[mid]; + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } +} diff --git a/core/java/android/util/LayoutDirection.java b/core/java/android/util/LayoutDirection.java new file mode 100644 index 0000000000000000000000000000000000000000..20af20b6500942d1ea311c9e6d1585bdbf2b0b0f --- /dev/null +++ b/core/java/android/util/LayoutDirection.java @@ -0,0 +1,48 @@ +/* + * 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.util; + +/** + * 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 final class LayoutDirection { + + // No instantiation + private LayoutDirection() {} + + /** + * Horizontal layout direction is from Left to Right. + */ + public static final int LTR = 0; + + /** + * Horizontal layout direction is from Right to Left. + */ + public static final int RTL = 1; + + /** + * Horizontal layout direction is inherited. + */ + public static final int INHERIT = 2; + + /** + * Horizontal layout direction is deduced from the default language script for the locale. + */ + public static final int LOCALE = 3; +} diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 1c3709f9541afbd23180c7c0659b9ac3050222c4..abd173a507216ace0976c3d0edaffee1c804dfd0 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -17,6 +17,7 @@ package android.util; import com.android.internal.os.RuntimeInit; +import com.android.internal.util.FastPrintWriter; import java.io.PrintWriter; import java.io.StringWriter; @@ -83,14 +84,14 @@ public final class Log { public static final int ASSERT = 7; /** - * Exception class used to capture a stack trace in {@link #wtf()}. + * Exception class used to capture a stack trace in {@link #wtf}. */ private static class TerribleFailure extends Exception { TerribleFailure(String msg, Throwable cause) { super(msg, cause); } } /** - * Interface to handle terrible failures from {@link #wtf()}. + * Interface to handle terrible failures from {@link #wtf}. * * @hide */ @@ -252,7 +253,16 @@ public final class Log { * @param msg The message you would like logged. */ public static int wtf(String tag, String msg) { - return wtf(tag, msg, null); + return wtf(LOG_ID_MAIN, tag, msg, null, false); + } + + /** + * Like {@link #wtf(String, String)}, but also writes to the log the full + * call stack. + * @hide + */ + public static int wtfStack(String tag, String msg) { + return wtf(LOG_ID_MAIN, tag, msg, null, true); } /** @@ -262,7 +272,7 @@ public final class Log { * @param tr An exception to log. */ public static int wtf(String tag, Throwable tr) { - return wtf(tag, tr.getMessage(), tr); + return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false); } /** @@ -273,8 +283,13 @@ public final class Log { * @param tr An exception to log. May be null. */ public static int wtf(String tag, String msg, Throwable tr) { + return wtf(LOG_ID_MAIN, tag, msg, tr, false); + } + + static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack) { TerribleFailure what = new TerribleFailure(msg, tr); - int bytes = println_native(LOG_ID_MAIN, ASSERT, tag, msg + '\n' + getStackTraceString(tr)); + int bytes = println_native(logId, ASSERT, tag, msg + '\n' + + getStackTraceString(localStack ? what : tr)); sWtfHandler.onTerribleFailure(tag, what); return bytes; } @@ -315,8 +330,9 @@ public final class Log { } StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); + PrintWriter pw = new FastPrintWriter(sw, false, 256); tr.printStackTrace(pw); + pw.flush(); return sw.toString(); } diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java index 630e5f37d8a026f93f5f092d94b21a7c4fb034b7..d6e116f21e3feeed8fd53e0ec353c567183ec9ad 100644 --- a/core/java/android/util/LongSparseArray.java +++ b/core/java/android/util/LongSparseArray.java @@ -20,8 +20,31 @@ import com.android.internal.util.ArrayUtils; /** * SparseArray mapping longs to Objects. Unlike a normal array of Objects, - * there can be gaps in the indices. It is intended to be more efficient - * than using a HashMap to map Longs to Objects. + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Longs to Objects, both because it avoids + * auto-boxing keys and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

    Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    To help with performance, the container includes an optimization when removing + * keys: instead of compacting its array immediately, it leaves the removed entry marked + * as deleted. The entry can then be re-used for the same key, or compacted later in + * a single garbage collection step of all removed entries. This garbage collection will + * need to be performed at any time the array needs to be grown or the the map size or + * entry values are retrieved.

    + * + *

    It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

    */ public class LongSparseArray implements Cloneable { private static final Object DELETED = new Object(); @@ -41,13 +64,19 @@ public class LongSparseArray implements Cloneable { /** * Creates a new LongSparseArray containing no mappings that will not * require any additional memory allocation to store the specified - * number of mappings. + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. */ public LongSparseArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); - - mKeys = new long[initialCapacity]; - mValues = new Object[initialCapacity]; + if (initialCapacity == 0) { + mKeys = ContainerHelpers.EMPTY_LONGS; + mValues = ContainerHelpers.EMPTY_OBJECTS; + } else { + initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); + mKeys = new long[initialCapacity]; + mValues = new Object[initialCapacity]; + } mSize = 0; } @@ -79,7 +108,7 @@ public class LongSparseArray implements Cloneable { */ @SuppressWarnings("unchecked") public E get(long key, E valueIfKeyNotFound) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i < 0 || mValues[i] == DELETED) { return valueIfKeyNotFound; @@ -92,7 +121,7 @@ public class LongSparseArray implements Cloneable { * Removes the mapping from the specified key, if there was any. */ public void delete(long key) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { if (mValues[i] != DELETED) { @@ -153,7 +182,7 @@ public class LongSparseArray implements Cloneable { * was one. */ public void put(long key, E value) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { mValues[i] = value; @@ -170,7 +199,7 @@ public class LongSparseArray implements Cloneable { gc(); // Search again because indices may have changed. - i = ~binarySearch(mKeys, 0, mSize, key); + i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); } if (mSize >= mKeys.length) { @@ -215,6 +244,11 @@ public class LongSparseArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the key from the indexth key-value mapping that this * LongSparseArray stores. + * + *

    The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

    */ public long keyAt(int index) { if (mGarbage) { @@ -228,6 +262,12 @@ public class LongSparseArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the value from the indexth key-value mapping that this * LongSparseArray stores. + * + *

    The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

    */ @SuppressWarnings("unchecked") public E valueAt(int index) { @@ -261,7 +301,7 @@ public class LongSparseArray implements Cloneable { gc(); } - return binarySearch(mKeys, 0, mSize, key); + return ContainerHelpers.binarySearch(mKeys, mSize, key); } /** @@ -333,23 +373,36 @@ public class LongSparseArray implements Cloneable { mSize = pos + 1; } - private static int binarySearch(long[] a, int start, int len, long key) { - int high = start + len, low = start - 1, guess; - - while (high - low > 1) { - guess = (high + low) / 2; - - if (a[guess] < key) - low = guess; - else - high = guess; + /** + * {@inheritDoc} + * + *

    This implementation composes a string by iterating over its mappings. If + * this map contains itself as a value, the string "(this Map)" + * will appear in its place. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; } - if (high == start + len) - return ~(start + len); - else if (a[high] == key) - return high; - else - return ~high; + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + long key = keyAt(i); + buffer.append(key); + buffer.append('='); + Object value = valueAt(i); + if (value != this) { + buffer.append(value); + } else { + buffer.append("(this Map)"); + } + } + buffer.append('}'); + return buffer.toString(); } } diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java index 34b61264d653fee3b0febc5d635d1a7217bd394f..87d868b02b44adf56763a026fbeef3000032e16e 100644 --- a/core/java/android/util/LongSparseLongArray.java +++ b/core/java/android/util/LongSparseLongArray.java @@ -22,8 +22,24 @@ import java.util.Arrays; /** * Map of {@code long} to {@code long}. Unlike a normal array of longs, there - * can be gaps in the indices. It is intended to be more efficient than using a - * {@code HashMap}. + * can be gaps in the indices. It is intended to be more memory efficient than using a + * {@code HashMap}, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

    Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

    * * @hide */ @@ -42,13 +58,19 @@ public class LongSparseLongArray implements Cloneable { /** * Creates a new SparseLongArray containing no mappings that will not * require any additional memory allocation to store the specified - * number of mappings. + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. */ public LongSparseLongArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); - - mKeys = new long[initialCapacity]; - mValues = new long[initialCapacity]; + if (initialCapacity == 0) { + mKeys = ContainerHelpers.EMPTY_LONGS; + mValues = ContainerHelpers.EMPTY_LONGS; + } else { + initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); + mKeys = new long[initialCapacity]; + mValues = new long[initialCapacity]; + } mSize = 0; } @@ -78,7 +100,7 @@ public class LongSparseLongArray implements Cloneable { * if no such mapping has been made. */ public long get(long key, long valueIfKeyNotFound) { - int i = Arrays.binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i < 0) { return valueIfKeyNotFound; @@ -91,7 +113,7 @@ public class LongSparseLongArray implements Cloneable { * Removes the mapping from the specified key, if there was any. */ public void delete(long key) { - int i = Arrays.binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { removeAt(i); @@ -113,7 +135,7 @@ public class LongSparseLongArray implements Cloneable { * was one. */ public void put(long key, long value) { - int i = Arrays.binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { mValues[i] = value; @@ -147,6 +169,11 @@ public class LongSparseLongArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the key from the indexth key-value mapping that this * SparseLongArray stores. + * + *

    The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

    */ public long keyAt(int index) { return mKeys[index]; @@ -156,6 +183,12 @@ public class LongSparseLongArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the value from the indexth key-value mapping that this * SparseLongArray stores. + * + *

    The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

    */ public long valueAt(int index) { return mValues[index]; @@ -167,7 +200,7 @@ public class LongSparseLongArray implements Cloneable { * key is not mapped. */ public int indexOfKey(long key) { - return Arrays.binarySearch(mKeys, 0, mSize, key); + return ContainerHelpers.binarySearch(mKeys, mSize, key); } /** @@ -225,4 +258,31 @@ public class LongSparseLongArray implements Cloneable { mKeys = nkeys; mValues = nvalues; } + + /** + * {@inheritDoc} + * + *

    This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + long key = keyAt(i); + buffer.append(key); + buffer.append('='); + long value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); + } } diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java new file mode 100644 index 0000000000000000000000000000000000000000..f4a9b0bbb73e9a09e4f6daa6cbc2fb79e68925df --- /dev/null +++ b/core/java/android/util/MapCollections.java @@ -0,0 +1,559 @@ +/* + * 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.util; + +import libcore.util.Objects; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Helper for writing standard Java collection interfaces to a data + * structure like {@link ArrayMap}. + * @hide + */ +abstract class MapCollections { + EntrySet mEntrySet; + KeySet mKeySet; + ValuesCollection mValues; + + final class ArrayIterator implements Iterator { + final int mOffset; + int mSize; + int mIndex; + boolean mCanRemove = false; + + ArrayIterator(int offset) { + mOffset = offset; + mSize = colGetSize(); + } + + @Override + public boolean hasNext() { + return mIndex < mSize; + } + + @Override + public T next() { + Object res = colGetEntry(mIndex, mOffset); + mIndex++; + mCanRemove = true; + return (T)res; + } + + @Override + public void remove() { + if (!mCanRemove) { + throw new IllegalStateException(); + } + mIndex--; + mSize--; + mCanRemove = false; + colRemoveAt(mIndex); + } + } + + final class MapIterator implements Iterator>, Map.Entry { + int mEnd; + int mIndex; + boolean mEntryValid = false; + + MapIterator() { + mEnd = colGetSize() - 1; + mIndex = -1; + } + + @Override + public boolean hasNext() { + return mIndex < mEnd; + } + + @Override + public Map.Entry next() { + mIndex++; + mEntryValid = true; + return this; + } + + @Override + public void remove() { + if (!mEntryValid) { + throw new IllegalStateException(); + } + mIndex--; + mEnd--; + mEntryValid = false; + colRemoveAt(mIndex); + } + + @Override + public K getKey() { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + return (K)colGetEntry(mIndex, 0); + } + + @Override + public V getValue() { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + return (V)colGetEntry(mIndex, 1); + } + + @Override + public V setValue(V object) { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + return colSetValue(mIndex, object); + } + + @Override + public final boolean equals(Object o) { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry e = (Map.Entry) o; + return Objects.equal(e.getKey(), colGetEntry(mIndex, 0)) + && Objects.equal(e.getValue(), colGetEntry(mIndex, 1)); + } + + @Override + public final int hashCode() { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + final Object key = colGetEntry(mIndex, 0); + final Object value = colGetEntry(mIndex, 1); + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + @Override + public final String toString() { + return getKey() + "=" + getValue(); + } + } + + final class EntrySet implements Set> { + @Override + public boolean add(Map.Entry object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection> collection) { + int oldSize = colGetSize(); + for (Map.Entry entry : collection) { + colPut(entry.getKey(), entry.getValue()); + } + return oldSize != colGetSize(); + } + + @Override + public void clear() { + colClear(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + int index = colIndexOfKey(e.getKey()); + if (index < 0) { + return false; + } + Object foundVal = colGetEntry(index, 1); + return Objects.equal(foundVal, e.getValue()); + } + + @Override + public boolean containsAll(Collection collection) { + Iterator it = collection.iterator(); + while (it.hasNext()) { + if (!contains(it.next())) { + return false; + } + } + return true; + } + + @Override + public boolean isEmpty() { + return colGetSize() == 0; + } + + @Override + public Iterator> iterator() { + return new MapIterator(); + } + + @Override + public boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + return colGetSize(); + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(T[] array) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object object) { + return equalsSetHelper(this, object); + } + + @Override + public int hashCode() { + int result = 0; + for (int i=colGetSize()-1; i>=0; i--) { + final Object key = colGetEntry(i, 0); + final Object value = colGetEntry(i, 1); + result += ( (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()) ); + } + return result; + } + }; + + final class KeySet implements Set { + + @Override + public boolean add(K object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + colClear(); + } + + @Override + public boolean contains(Object object) { + return colIndexOfKey(object) >= 0; + } + + @Override + public boolean containsAll(Collection collection) { + return containsAllHelper(colGetMap(), collection); + } + + @Override + public boolean isEmpty() { + return colGetSize() == 0; + } + + @Override + public Iterator iterator() { + return new ArrayIterator(0); + } + + @Override + public boolean remove(Object object) { + int index = colIndexOfKey(object); + if (index >= 0) { + colRemoveAt(index); + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection collection) { + return removeAllHelper(colGetMap(), collection); + } + + @Override + public boolean retainAll(Collection collection) { + return retainAllHelper(colGetMap(), collection); + } + + @Override + public int size() { + return colGetSize(); + } + + @Override + public Object[] toArray() { + return toArrayHelper(0); + } + + @Override + public T[] toArray(T[] array) { + return toArrayHelper(array, 0); + } + + @Override + public boolean equals(Object object) { + return equalsSetHelper(this, object); + } + + @Override + public int hashCode() { + int result = 0; + for (int i=colGetSize()-1; i>=0; i--) { + Object obj = colGetEntry(i, 0); + result += obj == null ? 0 : obj.hashCode(); + } + return result; + } + }; + + final class ValuesCollection implements Collection { + + @Override + public boolean add(V object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + colClear(); + } + + @Override + public boolean contains(Object object) { + return colIndexOfValue(object) >= 0; + } + + @Override + public boolean containsAll(Collection collection) { + Iterator it = collection.iterator(); + while (it.hasNext()) { + if (!contains(it.next())) { + return false; + } + } + return true; + } + + @Override + public boolean isEmpty() { + return colGetSize() == 0; + } + + @Override + public Iterator iterator() { + return new ArrayIterator(1); + } + + @Override + public boolean remove(Object object) { + int index = colIndexOfValue(object); + if (index >= 0) { + colRemoveAt(index); + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection collection) { + int N = colGetSize(); + boolean changed = false; + for (int i=0; i collection) { + int N = colGetSize(); + boolean changed = false; + for (int i=0; i T[] toArray(T[] array) { + return toArrayHelper(array, 1); + } + }; + + public static boolean containsAllHelper(Map map, Collection collection) { + Iterator it = collection.iterator(); + while (it.hasNext()) { + if (!map.containsKey(it.next())) { + return false; + } + } + return true; + } + + public static boolean removeAllHelper(Map map, Collection collection) { + int oldSize = map.size(); + Iterator it = collection.iterator(); + while (it.hasNext()) { + map.remove(it.next()); + } + return oldSize != map.size(); + } + + public static boolean retainAllHelper(Map map, Collection collection) { + int oldSize = map.size(); + Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + if (!collection.contains(it.next())) { + it.remove(); + } + } + return oldSize != map.size(); + } + + public Object[] toArrayHelper(int offset) { + final int N = colGetSize(); + Object[] result = new Object[N]; + for (int i=0; i T[] toArrayHelper(T[] array, int offset) { + final int N = colGetSize(); + if (array.length < N) { + @SuppressWarnings("unchecked") T[] newArray + = (T[]) Array.newInstance(array.getClass().getComponentType(), N); + array = newArray; + } + for (int i=0; i N) { + array[N] = null; + } + return array; + } + + public static boolean equalsSetHelper(Set set, Object object) { + if (set == object) { + return true; + } + if (object instanceof Set) { + Set s = (Set) object; + + try { + return set.size() == s.size() && set.containsAll(s); + } catch (NullPointerException ignored) { + return false; + } catch (ClassCastException ignored) { + return false; + } + } + return false; + } + + public Set> getEntrySet() { + if (mEntrySet == null) { + mEntrySet = new EntrySet(); + } + return mEntrySet; + } + + public Set getKeySet() { + if (mKeySet == null) { + mKeySet = new KeySet(); + } + return mKeySet; + } + + public Collection getValues() { + if (mValues == null) { + mValues = new ValuesCollection(); + } + return mValues; + } + + protected abstract int colGetSize(); + protected abstract Object colGetEntry(int index, int offset); + protected abstract int colIndexOfKey(Object key); + protected abstract int colIndexOfValue(Object key); + protected abstract Map colGetMap(); + protected abstract void colPut(K key, V value); + protected abstract V colSetValue(int index, V value); + protected abstract void colRemoveAt(int index); + protected abstract void colClear(); +} diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java index ecf5ea153c2eef9e8c5459d2327442dfd2514133..70795bbc43c0135cd4421beabb6e9f03ca2768d1 100644 --- a/core/java/android/util/Slog.java +++ b/core/java/android/util/Slog.java @@ -78,6 +78,22 @@ public final class Slog { msg + '\n' + Log.getStackTraceString(tr)); } + public static int wtf(String tag, String msg) { + return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false); + } + + public static int wtfStack(String tag, String msg) { + return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true); + } + + public static int wtf(String tag, Throwable tr) { + return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false); + } + + public static int wtf(String tag, String msg, Throwable tr) { + return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false); + } + public static int println(int priority, String tag, String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg); } diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index 7e8fee56ea95490509cafed01ea18712669b8d0f..6e168a8b4ccd7d5b612a03658eac4cdccde740d8 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -20,8 +20,31 @@ import com.android.internal.util.ArrayUtils; /** * SparseArrays map integers to Objects. Unlike a normal array of Objects, - * there can be gaps in the indices. It is intended to be more efficient - * than using a HashMap to map Integers to Objects. + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Objects, both because it avoids + * auto-boxing keys and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

    Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    To help with performance, the container includes an optimization when removing + * keys: instead of compacting its array immediately, it leaves the removed entry marked + * as deleted. The entry can then be re-used for the same key, or compacted later in + * a single garbage collection step of all removed entries. This garbage collection will + * need to be performed at any time the array needs to be grown or the the map size or + * entry values are retrieved.

    + * + *

    It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

    */ public class SparseArray implements Cloneable { private static final Object DELETED = new Object(); @@ -41,13 +64,19 @@ public class SparseArray implements Cloneable { /** * Creates a new SparseArray containing no mappings that will not * require any additional memory allocation to store the specified - * number of mappings. + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. */ public SparseArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); - - mKeys = new int[initialCapacity]; - mValues = new Object[initialCapacity]; + if (initialCapacity == 0) { + mKeys = ContainerHelpers.EMPTY_INTS; + mValues = ContainerHelpers.EMPTY_OBJECTS; + } else { + initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); + mKeys = new int[initialCapacity]; + mValues = new Object[initialCapacity]; + } mSize = 0; } @@ -79,7 +108,7 @@ public class SparseArray implements Cloneable { */ @SuppressWarnings("unchecked") public E get(int key, E valueIfKeyNotFound) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i < 0 || mValues[i] == DELETED) { return valueIfKeyNotFound; @@ -92,7 +121,7 @@ public class SparseArray implements Cloneable { * Removes the mapping from the specified key, if there was any. */ public void delete(int key) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { if (mValues[i] != DELETED) { @@ -119,6 +148,19 @@ public class SparseArray implements Cloneable { } } + /** + * Remove a range of mappings as a batch. + * + * @param index Index to begin at + * @param size Number of mappings to remove + */ + public void removeAtRange(int index, int size) { + final int end = Math.min(mSize, index + size); + for (int i = index; i < end; i++) { + removeAt(i); + } + } + private void gc() { // Log.e("SparseArray", "gc start with " + mSize); @@ -153,7 +195,7 @@ public class SparseArray implements Cloneable { * was one. */ public void put(int key, E value) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { mValues[i] = value; @@ -170,7 +212,7 @@ public class SparseArray implements Cloneable { gc(); // Search again because indices may have changed. - i = ~binarySearch(mKeys, 0, mSize, key); + i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); } if (mSize >= mKeys.length) { @@ -215,6 +257,11 @@ public class SparseArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the key from the indexth key-value mapping that this * SparseArray stores. + * + *

    The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

    */ public int keyAt(int index) { if (mGarbage) { @@ -228,6 +275,12 @@ public class SparseArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the value from the indexth key-value mapping that this * SparseArray stores. + * + *

    The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

    */ @SuppressWarnings("unchecked") public E valueAt(int index) { @@ -261,7 +314,7 @@ public class SparseArray implements Cloneable { gc(); } - return binarySearch(mKeys, 0, mSize, key); + return ContainerHelpers.binarySearch(mKeys, mSize, key); } /** @@ -335,23 +388,36 @@ public class SparseArray implements Cloneable { mSize = pos + 1; } - private static int binarySearch(int[] a, int start, int len, int key) { - int high = start + len, low = start - 1, guess; - - while (high - low > 1) { - guess = (high + low) / 2; - - if (a[guess] < key) - low = guess; - else - high = guess; + /** + * {@inheritDoc} + * + *

    This implementation composes a string by iterating over its mappings. If + * this map contains itself as a value, the string "(this Map)" + * will appear in its place. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; } - if (high == start + len) - return ~(start + len); - else if (a[high] == key) - return high; - else - return ~high; + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + Object value = valueAt(i); + if (value != this) { + buffer.append(value); + } else { + buffer.append("(this Map)"); + } + } + buffer.append('}'); + return buffer.toString(); } } diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java index 76c47c6aabade47bc9ae0ae132226a512012da33..68487e391710142699ce0bc0cdd622afe43e5a27 100644 --- a/core/java/android/util/SparseBooleanArray.java +++ b/core/java/android/util/SparseBooleanArray.java @@ -21,8 +21,24 @@ import com.android.internal.util.ArrayUtils; /** * SparseBooleanArrays map integers to booleans. * Unlike a normal array of booleans - * there can be gaps in the indices. It is intended to be more efficient - * than using a HashMap to map Integers to Booleans. + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Booleans, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

    Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

    */ public class SparseBooleanArray implements Cloneable { /** @@ -35,13 +51,19 @@ public class SparseBooleanArray implements Cloneable { /** * Creates a new SparseBooleanArray containing no mappings that will not * require any additional memory allocation to store the specified - * number of mappings. + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. */ public SparseBooleanArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); - - mKeys = new int[initialCapacity]; - mValues = new boolean[initialCapacity]; + if (initialCapacity == 0) { + mKeys = ContainerHelpers.EMPTY_INTS; + mValues = ContainerHelpers.EMPTY_BOOLEANS; + } else { + initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); + mKeys = new int[initialCapacity]; + mValues = new boolean[initialCapacity]; + } mSize = 0; } @@ -71,7 +93,7 @@ public class SparseBooleanArray implements Cloneable { * if no such mapping has been made. */ public boolean get(int key, boolean valueIfKeyNotFound) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i < 0) { return valueIfKeyNotFound; @@ -84,7 +106,7 @@ public class SparseBooleanArray implements Cloneable { * Removes the mapping from the specified key, if there was any. */ public void delete(int key) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1)); @@ -99,7 +121,7 @@ public class SparseBooleanArray implements Cloneable { * was one. */ public void put(int key, boolean value) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { mValues[i] = value; @@ -143,16 +165,27 @@ public class SparseBooleanArray implements Cloneable { /** * Given an index in the range 0...size()-1, returns * the key from the indexth key-value mapping that this - * SparseBooleanArray stores. + * SparseBooleanArray stores. + * + *

    The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

    */ public int keyAt(int index) { return mKeys[index]; } - + /** * Given an index in the range 0...size()-1, returns * the value from the indexth key-value mapping that this - * SparseBooleanArray stores. + * SparseBooleanArray stores. + * + *

    The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

    */ public boolean valueAt(int index) { return mValues[index]; @@ -164,7 +197,7 @@ public class SparseBooleanArray implements Cloneable { * key is not mapped. */ public int indexOfKey(int key) { - return binarySearch(mKeys, 0, mSize, key); + return ContainerHelpers.binarySearch(mKeys, mSize, key); } /** @@ -219,25 +252,32 @@ public class SparseBooleanArray implements Cloneable { mValues[pos] = value; mSize = pos + 1; } - - private static int binarySearch(int[] a, int start, int len, int key) { - int high = start + len, low = start - 1, guess; - while (high - low > 1) { - guess = (high + low) / 2; - - if (a[guess] < key) - low = guess; - else - high = guess; + /** + * {@inheritDoc} + * + *

    This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; } - if (high == start + len) - return ~(start + len); - else if (a[high] == key) - return high; - else - return ~high; + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + boolean value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); } private int[] mKeys; diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java index 8d111770e8dd71b9e930fd7b9a4918d95310a3d1..0835cb02e032d30bbc7a0df10cf09191ed54263b 100644 --- a/core/java/android/util/SparseIntArray.java +++ b/core/java/android/util/SparseIntArray.java @@ -20,11 +20,26 @@ import com.android.internal.util.ArrayUtils; /** * SparseIntArrays map integers to integers. Unlike a normal array of integers, - * there can be gaps in the indices. It is intended to be more efficient - * than using a HashMap to map Integers to Integers. + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Integers, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

    Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

    */ public class SparseIntArray implements Cloneable { - private int[] mKeys; private int[] mValues; private int mSize; @@ -39,13 +54,19 @@ public class SparseIntArray implements Cloneable { /** * Creates a new SparseIntArray containing no mappings that will not * require any additional memory allocation to store the specified - * number of mappings. + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. */ public SparseIntArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); - - mKeys = new int[initialCapacity]; - mValues = new int[initialCapacity]; + if (initialCapacity == 0) { + mKeys = ContainerHelpers.EMPTY_INTS; + mValues = ContainerHelpers.EMPTY_INTS; + } else { + initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); + mKeys = new int[initialCapacity]; + mValues = new int[initialCapacity]; + } mSize = 0; } @@ -75,7 +96,7 @@ public class SparseIntArray implements Cloneable { * if no such mapping has been made. */ public int get(int key, int valueIfKeyNotFound) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i < 0) { return valueIfKeyNotFound; @@ -88,7 +109,7 @@ public class SparseIntArray implements Cloneable { * Removes the mapping from the specified key, if there was any. */ public void delete(int key) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { removeAt(i); @@ -110,7 +131,7 @@ public class SparseIntArray implements Cloneable { * was one. */ public void put(int key, int value) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { mValues[i] = value; @@ -154,16 +175,27 @@ public class SparseIntArray implements Cloneable { /** * Given an index in the range 0...size()-1, returns * the key from the indexth key-value mapping that this - * SparseIntArray stores. + * SparseIntArray stores. + * + *

    The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

    */ public int keyAt(int index) { return mKeys[index]; } - + /** * Given an index in the range 0...size()-1, returns * the value from the indexth key-value mapping that this - * SparseIntArray stores. + * SparseIntArray stores. + * + *

    The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

    */ public int valueAt(int index) { return mValues[index]; @@ -175,7 +207,7 @@ public class SparseIntArray implements Cloneable { * key is not mapped. */ public int indexOfKey(int key) { - return binarySearch(mKeys, 0, mSize, key); + return ContainerHelpers.binarySearch(mKeys, mSize, key); } /** @@ -230,24 +262,31 @@ public class SparseIntArray implements Cloneable { mValues[pos] = value; mSize = pos + 1; } - - private static int binarySearch(int[] a, int start, int len, int key) { - int high = start + len, low = start - 1, guess; - while (high - low > 1) { - guess = (high + low) / 2; - - if (a[guess] < key) - low = guess; - else - high = guess; + /** + * {@inheritDoc} + * + *

    This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; } - if (high == start + len) - return ~(start + len); - else if (a[high] == key) - return high; - else - return ~high; + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + int value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); } } diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java index 2f7a6fe63bb5844342808202afab17d7b6220bf3..62c1c0d9b6fa0f84a6f45c02879759626c6a9320 100644 --- a/core/java/android/util/SparseLongArray.java +++ b/core/java/android/util/SparseLongArray.java @@ -20,11 +20,26 @@ import com.android.internal.util.ArrayUtils; /** * SparseLongArrays map integers to longs. Unlike a normal array of longs, - * there can be gaps in the indices. It is intended to be more efficient - * than using a HashMap to map Integers to Longs. + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Longs, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

    Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

    + * + *

    It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

    */ public class SparseLongArray implements Cloneable { - private int[] mKeys; private long[] mValues; private int mSize; @@ -39,13 +54,19 @@ public class SparseLongArray implements Cloneable { /** * Creates a new SparseLongArray containing no mappings that will not * require any additional memory allocation to store the specified - * number of mappings. + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. */ public SparseLongArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); - - mKeys = new int[initialCapacity]; - mValues = new long[initialCapacity]; + if (initialCapacity == 0) { + mKeys = ContainerHelpers.EMPTY_INTS; + mValues = ContainerHelpers.EMPTY_LONGS; + } else { + initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); + mKeys = new int[initialCapacity]; + mValues = new long[initialCapacity]; + } mSize = 0; } @@ -75,7 +96,7 @@ public class SparseLongArray implements Cloneable { * if no such mapping has been made. */ public long get(int key, long valueIfKeyNotFound) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i < 0) { return valueIfKeyNotFound; @@ -88,7 +109,7 @@ public class SparseLongArray implements Cloneable { * Removes the mapping from the specified key, if there was any. */ public void delete(int key) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { removeAt(i); @@ -110,7 +131,7 @@ public class SparseLongArray implements Cloneable { * was one. */ public void put(int key, long value) { - int i = binarySearch(mKeys, 0, mSize, key); + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { mValues[i] = value; @@ -144,6 +165,11 @@ public class SparseLongArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the key from the indexth key-value mapping that this * SparseLongArray stores. + * + *

    The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

    */ public int keyAt(int index) { return mKeys[index]; @@ -153,6 +179,12 @@ public class SparseLongArray implements Cloneable { * Given an index in the range 0...size()-1, returns * the value from the indexth key-value mapping that this * SparseLongArray stores. + * + *

    The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

    */ public long valueAt(int index) { return mValues[index]; @@ -164,7 +196,7 @@ public class SparseLongArray implements Cloneable { * key is not mapped. */ public int indexOfKey(int key) { - return binarySearch(mKeys, 0, mSize, key); + return ContainerHelpers.binarySearch(mKeys, mSize, key); } /** @@ -223,23 +255,30 @@ public class SparseLongArray implements Cloneable { mValues = nvalues; } - private static int binarySearch(int[] a, int start, int len, long key) { - int high = start + len, low = start - 1, guess; - - while (high - low > 1) { - guess = (high + low) / 2; - - if (a[guess] < key) - low = guess; - else - high = guess; + /** + * {@inheritDoc} + * + *

    This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; } - if (high == start + len) - return ~(start + len); - else if (a[high] == key) - return high; - else - return ~high; + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + long value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); } } diff --git a/core/java/android/util/SuperNotCalledException.java b/core/java/android/util/SuperNotCalledException.java new file mode 100644 index 0000000000000000000000000000000000000000..18361420b8138aaa6a2e1a79ca4bd58cc611c2f5 --- /dev/null +++ b/core/java/android/util/SuperNotCalledException.java @@ -0,0 +1,27 @@ +/* + * 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.util; + +/** + * @hide + */ +public final class SuperNotCalledException extends AndroidRuntimeException { + public SuperNotCalledException(String msg) { + super(msg); + } +} diff --git a/core/java/android/util/TimedRemoteCaller.java b/core/java/android/util/TimedRemoteCaller.java new file mode 100644 index 0000000000000000000000000000000000000000..abb2b6401d7173a3f6bf1270efe8c06d275c1d75 --- /dev/null +++ b/core/java/android/util/TimedRemoteCaller.java @@ -0,0 +1,134 @@ +/* + * 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.util; + +import android.os.SystemClock; + +import java.util.concurrent.TimeoutException; + +/** + * This is a helper class for making an async one way call and + * its async one way response response in a sync fashion within + * a timeout. The key idea is to call the remote method with a + * sequence number and a callback and then starting to wait for + * the response. The remote method calls back with the result and + * the sequence number. If the response comes within the timeout + * and its sequence number is the one sent in the method invocation, + * then the call succeeded. If the response does not come within + * the timeout then the call failed. Older result received when + * waiting for the result are ignored. + *

    + * Typical usage is: + *

    + *

    
    + * public class MyMethodCaller extends TimeoutRemoteCallHelper {
    + *     // The one way remote method to call.
    + *     private final IRemoteInterface mTarget;
    + *
    + *     // One way callback invoked when the remote method is done.
    + *     private final IRemoteCallback mCallback = new IRemoteCallback.Stub() {
    + *         public void onCompleted(Object result, int sequence) {
    + *             onRemoteMethodResult(result, sequence);
    + *         }
    + *     };
    + *
    + *     public MyMethodCaller(IRemoteInterface target) {
    + *         mTarget = target;
    + *     }
    + *
    + *     public Object onCallMyMethod(Object arg) throws RemoteException {
    + *         final int sequence = onBeforeRemoteCall();
    + *         mTarget.myMethod(arg, sequence);
    + *         return getResultTimed(sequence);
    + *     }
    + * }
    + * 

    + * + * @param The type of the expected result. + * + * @hide + */ +public abstract class TimedRemoteCaller { + + public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000; + + private static final int UNDEFINED_SEQUENCE = -1; + + private final Object mLock = new Object(); + + private final long mCallTimeoutMillis; + + private int mSequenceCounter; + + private int mReceivedSequence = UNDEFINED_SEQUENCE; + + private int mAwaitedSequence = UNDEFINED_SEQUENCE; + + private T mResult; + + public TimedRemoteCaller(long callTimeoutMillis) { + mCallTimeoutMillis = callTimeoutMillis; + } + + public final int onBeforeRemoteCall() { + synchronized (mLock) { + mAwaitedSequence = mSequenceCounter++; + return mAwaitedSequence; + } + } + + public final T getResultTimed(int sequence) throws TimeoutException { + synchronized (mLock) { + final boolean success = waitForResultTimedLocked(sequence); + if (!success) { + throw new TimeoutException("No reponse for sequence: " + sequence); + } + T result = mResult; + mResult = null; + return result; + } + } + + public final void onRemoteMethodResult(T result, int sequence) { + synchronized (mLock) { + if (sequence == mAwaitedSequence) { + mReceivedSequence = sequence; + mResult = result; + mLock.notifyAll(); + } + } + } + + private boolean waitForResultTimedLocked(int sequence) { + final long startMillis = SystemClock.uptimeMillis(); + while (true) { + try { + if (mReceivedSequence == sequence) { + return true; + } + final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + final long waitMillis = mCallTimeoutMillis - elapsedMillis; + if (waitMillis <= 0) { + return false; + } + mLock.wait(waitMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } +} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 2d6453e67b5d5aa17e55975c31ac0a3dc341afd8..41d37001b84889252f03066e9dd5cbd60fb8de5c 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -406,6 +406,10 @@ final class AccessibilityInteractionController { if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { break; } + // The focused view not shown, we failed. + if (!isShown(host)) { + break; + } // If the host has a provider ask this provider to search for the // focus instead fetching all provider nodes to do the search here. AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); @@ -419,9 +423,15 @@ final class AccessibilityInteractionController { } } break; case AccessibilityNodeInfo.FOCUS_INPUT: { - // Input focus cannot go to virtual views. View target = root.findFocus(); - if (target != null && isShown(target)) { + if (target == null || !isShown(target)) { + break; + } + AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); + if (provider != null) { + focused = provider.findFocus(focusType); + } + if (focused == null) { focused = target.createAccessibilityNodeInfo(); } } break; diff --git a/core/java/android/view/CompatibilityInfoHolder.java b/core/java/android/view/CompatibilityInfoHolder.java deleted file mode 100644 index fc8d6841ac8fd78f6f67f574b74c2fa49d3ad258..0000000000000000000000000000000000000000 --- a/core/java/android/view/CompatibilityInfoHolder.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2011 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.view; - -import android.content.res.CompatibilityInfo; - -/** @hide */ -public class CompatibilityInfoHolder { - private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; - - public void set(CompatibilityInfo compatInfo) { - if (compatInfo != null && (compatInfo.isScalingRequired() - || !compatInfo.supportsScreen())) { - mCompatInfo = compatInfo; - } else { - mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; - } - } - - public CompatibilityInfo get() { - return mCompatInfo; - } - - public CompatibilityInfo getIfNeeded() { - CompatibilityInfo ci = mCompatInfo; - if (ci == null || ci == CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO) { - return null; - } - return ci; - } -} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index e6a7950b1dbf6bd5dd0a720f86969d6e43cbdf56..354ea6613a397fd861c310ff1e3f0a415d7c43c5 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -16,10 +16,12 @@ package android.view; +import android.content.res.CompatibilityInfo; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; +import android.os.Process; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; @@ -57,7 +59,9 @@ public final class Display { private final int mFlags; private final int mType; private final String mAddress; - private final CompatibilityInfoHolder mCompatibilityInfo; + private final int mOwnerUid; + private final String mOwnerPackageName; + private final DisplayAdjustments mDisplayAdjustments; private DisplayInfo mDisplayInfo; // never null private boolean mIsValid; @@ -142,6 +146,27 @@ public final class Display { */ public static final int FLAG_SECURE = 1 << 1; + /** + * Display flag: Indicates that the display is private. Only the application that + * owns the display can create windows on it. + * + * @see #getFlags + */ + public static final int FLAG_PRIVATE = 1 << 2; + + /** + * Display flag: Indicates that the display is a presentation display. + *

    + * This flag identifies secondary displays that are suitable for + * use as presentation displays such as HDMI or Wireless displays. Applications + * may automatically project their content to presentation displays to provide + * richer second screen experiences. + *

    + * + * @see #getFlags + */ + public static final int FLAG_PRESENTATION = 1 << 3; + /** * Display type: Unknown display type. * @hide @@ -172,6 +197,12 @@ public final class Display { */ public static final int TYPE_OVERLAY = 4; + /** + * Display type: Virtual display. + * @hide + */ + public static final int TYPE_VIRTUAL = 5; + /** * Internal method to create a display. * Applications should use {@link android.view.WindowManager#getDefaultDisplay()} @@ -182,11 +213,11 @@ public final class Display { */ public Display(DisplayManagerGlobal global, int displayId, DisplayInfo displayInfo /*not null*/, - CompatibilityInfoHolder compatibilityInfo) { + DisplayAdjustments daj) { mGlobal = global; mDisplayId = displayId; mDisplayInfo = displayInfo; - mCompatibilityInfo = compatibilityInfo; + mDisplayAdjustments = new DisplayAdjustments(daj); mIsValid = true; // Cache properties that cannot change as long as the display is valid. @@ -194,6 +225,8 @@ public final class Display { mFlags = displayInfo.flags; mType = displayInfo.type; mAddress = displayInfo.address; + mOwnerUid = displayInfo.ownerUid; + mOwnerPackageName = displayInfo.ownerPackageName; } /** @@ -262,6 +295,7 @@ public final class Display { * * @see #FLAG_SUPPORTS_PROTECTED_BUFFERS * @see #FLAG_SECURE + * @see #FLAG_PRIVATE */ public int getFlags() { return mFlags; @@ -277,6 +311,7 @@ public final class Display { * @see #TYPE_HDMI * @see #TYPE_WIFI * @see #TYPE_OVERLAY + * @see #TYPE_VIRTUAL * @hide */ public int getType() { @@ -294,14 +329,40 @@ public final class Display { return mAddress; } + /** + * Gets the UID of the application that owns this display, or zero if it is + * owned by the system. + *

    + * If the display is private, then only the owner can use it. + *

    + * + * @hide + */ + public int getOwnerUid() { + return mOwnerUid; + } + + /** + * Gets the package name of the application that owns this display, or null if it is + * owned by the system. + *

    + * If the display is private, then only the owner can use it. + *

    + * + * @hide + */ + public String getOwnerPackageName() { + return mOwnerPackageName; + } + /** * Gets the compatibility info used by this display instance. * - * @return The compatibility info holder, or null if none is required. + * @return The display adjustments holder, or null if none is required. * @hide */ - public CompatibilityInfoHolder getCompatibilityInfo() { - return mCompatibilityInfo; + public DisplayAdjustments getDisplayAdjustments() { + return mDisplayAdjustments; } /** @@ -342,7 +403,7 @@ public final class Display { public void getSize(Point outSize) { synchronized (this) { updateDisplayInfoLocked(); - mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments); outSize.x = mTempMetrics.widthPixels; outSize.y = mTempMetrics.heightPixels; } @@ -357,7 +418,7 @@ public final class Display { public void getRectSize(Rect outSize) { synchronized (this) { updateDisplayInfoLocked(); - mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments); outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels); } } @@ -522,7 +583,7 @@ public final class Display { public void getMetrics(DisplayMetrics outMetrics) { synchronized (this) { updateDisplayInfoLocked(); - mDisplayInfo.getAppMetrics(outMetrics, mCompatibilityInfo); + mDisplayInfo.getAppMetrics(outMetrics, mDisplayAdjustments); } } @@ -560,10 +621,28 @@ public final class Display { public void getRealMetrics(DisplayMetrics outMetrics) { synchronized (this) { updateDisplayInfoLocked(); - mDisplayInfo.getLogicalMetrics(outMetrics, null); + mDisplayInfo.getLogicalMetrics(outMetrics, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, + mDisplayAdjustments.getActivityToken()); } } + /** + * Returns true if the specified UID has access to this display. + * @hide + */ + public boolean hasAccess(int uid) { + return Display.hasAccess(uid, mFlags, mOwnerUid); + } + + /** @hide */ + public static boolean hasAccess(int uid, int flags, int ownerUid) { + return (flags & Display.FLAG_PRIVATE) == 0 + || uid == ownerUid + || uid == Process.SYSTEM_UID + || uid == 0; + } + private void updateDisplayInfoLocked() { // Note: The display manager caches display info objects on our behalf. DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId); @@ -591,7 +670,7 @@ public final class Display { long now = SystemClock.uptimeMillis(); if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) { updateDisplayInfoLocked(); - mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments); mCachedAppWidthCompat = mTempMetrics.widthPixels; mCachedAppHeightCompat = mTempMetrics.heightPixels; mLastCachedAppSizeUpdate = now; @@ -603,7 +682,7 @@ public final class Display { public String toString() { synchronized (this) { updateDisplayInfoLocked(); - mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments); return "Display id " + mDisplayId + ": " + mDisplayInfo + ", " + mTempMetrics + ", isValid=" + mIsValid; } @@ -624,6 +703,8 @@ public final class Display { return "WIFI"; case TYPE_OVERLAY: return "OVERLAY"; + case TYPE_VIRTUAL: + return "VIRTUAL"; default: return Integer.toString(type); } diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java new file mode 100644 index 0000000000000000000000000000000000000000..041d9e0479e1d1e51c5400a23271e012c35f4af6 --- /dev/null +++ b/core/java/android/view/DisplayAdjustments.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 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.view; + +import android.content.res.CompatibilityInfo; +import android.os.IBinder; + +import com.android.internal.util.Objects; + +/** @hide */ +public class DisplayAdjustments { + public static final boolean DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN = false; + + public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = new DisplayAdjustments(); + + private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + private volatile IBinder mActivityToken; + + public DisplayAdjustments() { + } + + public DisplayAdjustments(IBinder token) { + mActivityToken = token; + } + + public DisplayAdjustments(DisplayAdjustments daj) { + this (daj.getCompatibilityInfo(), daj.getActivityToken()); + } + + public DisplayAdjustments(CompatibilityInfo compatInfo, IBinder token) { + setCompatibilityInfo(compatInfo); + mActivityToken = token; + } + + public void setCompatibilityInfo(CompatibilityInfo compatInfo) { + if (this == DEFAULT_DISPLAY_ADJUSTMENTS) { + throw new IllegalArgumentException( + "setCompatbilityInfo: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS"); + } + if (compatInfo != null && (compatInfo.isScalingRequired() + || !compatInfo.supportsScreen())) { + mCompatInfo = compatInfo; + } else { + mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + } + } + + public CompatibilityInfo getCompatibilityInfo() { + return mCompatInfo; + } + + public void setActivityToken(IBinder token) { + if (this == DEFAULT_DISPLAY_ADJUSTMENTS) { + throw new IllegalArgumentException( + "setActivityToken: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS"); + } + mActivityToken = token; + } + + public IBinder getActivityToken() { + return mActivityToken; + } + + @Override + public int hashCode() { + int hash = 17; + hash = hash * 31 + mCompatInfo.hashCode(); + if (DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN) { + hash = hash * 31 + (mActivityToken == null ? 0 : mActivityToken.hashCode()); + } + return hash; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DisplayAdjustments)) { + return false; + } + DisplayAdjustments daj = (DisplayAdjustments)o; + return Objects.equal(daj.mCompatInfo, mCompatInfo) && + Objects.equal(daj.mActivityToken, mActivityToken); + } +} diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 9fcd9b1c8ac958591d1680c832a0f2b65ce9c490..8944207eb4475f2f20e405d617587f6b4270fb9d 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -17,8 +17,10 @@ package android.view; import android.content.res.CompatibilityInfo; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.util.DisplayMetrics; import libcore.util.Objects; @@ -177,6 +179,23 @@ public final class DisplayInfo implements Parcelable { */ public float physicalYDpi; + /** + * The UID of the application that owns this display, or zero if it is owned by the system. + *

    + * If the display is private, then only the owner can use it. + *

    + */ + public int ownerUid; + + /** + * The package name of the application that owns this display, or null if it is + * owned by the system. + *

    + * If the display is private, then only the owner can use it. + *

    + */ + public String ownerPackageName; + public static final Creator CREATOR = new Creator() { @Override public DisplayInfo createFromParcel(Parcel source) { @@ -228,7 +247,9 @@ public final class DisplayInfo implements Parcelable { && refreshRate == other.refreshRate && logicalDensityDpi == other.logicalDensityDpi && physicalXDpi == other.physicalXDpi - && physicalYDpi == other.physicalYDpi; + && physicalYDpi == other.physicalYDpi + && ownerUid == other.ownerUid + && Objects.equal(ownerPackageName, other.ownerPackageName); } @Override @@ -259,6 +280,8 @@ public final class DisplayInfo implements Parcelable { logicalDensityDpi = other.logicalDensityDpi; physicalXDpi = other.physicalXDpi; physicalYDpi = other.physicalYDpi; + ownerUid = other.ownerUid; + ownerPackageName = other.ownerPackageName; } public void readFromParcel(Parcel source) { @@ -284,6 +307,8 @@ public final class DisplayInfo implements Parcelable { logicalDensityDpi = source.readInt(); physicalXDpi = source.readFloat(); physicalYDpi = source.readFloat(); + ownerUid = source.readInt(); + ownerPackageName = source.readString(); } @Override @@ -310,6 +335,8 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(logicalDensityDpi); dest.writeFloat(physicalXDpi); dest.writeFloat(physicalYDpi); + dest.writeInt(ownerUid); + dest.writeString(ownerPackageName); } @Override @@ -317,12 +344,22 @@ public final class DisplayInfo implements Parcelable { return 0; } - public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) { - getMetricsWithSize(outMetrics, cih, appWidth, appHeight); + public void getAppMetrics(DisplayMetrics outMetrics) { + getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + } + + public void getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments) { + getMetricsWithSize(outMetrics, displayAdjustments.getCompatibilityInfo(), + displayAdjustments.getActivityToken(), appWidth, appHeight); + } + + public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci, IBinder token) { + getMetricsWithSize(outMetrics, ci, token, appWidth, appHeight); } - public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) { - getMetricsWithSize(outMetrics, cih, logicalWidth, logicalHeight); + public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, + IBinder token) { + getMetricsWithSize(outMetrics, compatInfo, token, logicalWidth, logicalHeight); } public int getNaturalWidth() { @@ -335,8 +372,15 @@ public final class DisplayInfo implements Parcelable { logicalHeight : logicalWidth; } - private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfoHolder cih, - int width, int height) { + /** + * Returns true if the specified UID has access to this display. + */ + public boolean hasAccess(int uid) { + return Display.hasAccess(uid, flags, ownerUid); + } + + private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, + IBinder token, int width, int height) { outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi; outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width; outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height; @@ -347,11 +391,8 @@ public final class DisplayInfo implements Parcelable { outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi; outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi; - if (cih != null) { - CompatibilityInfo ci = cih.getIfNeeded(); - if (ci != null) { - ci.applyToDisplayMetrics(outMetrics); - } + if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) { + compatInfo.applyToDisplayMetrics(outMetrics); } } @@ -402,8 +443,13 @@ public final class DisplayInfo implements Parcelable { sb.append(layerStack); sb.append(", type "); sb.append(Display.typeToString(type)); - sb.append(", address "); - sb.append(address); + if (address != null) { + sb.append(", address ").append(address); + } + if (ownerUid != 0 || ownerPackageName != null) { + sb.append(", owner ").append(ownerPackageName); + sb.append(" (uid ").append(ownerUid).append(")"); + } sb.append(flagsToString(flags)); sb.append("}"); return sb.toString(); @@ -417,6 +463,12 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) { result.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS"); } + if ((flags & Display.FLAG_PRIVATE) != 0) { + result.append(", FLAG_PRIVATE"); + } + if ((flags & Display.FLAG_PRESENTATION) != 0) { + result.append(", FLAG_PRESENTATION"); + } return result.toString(); } } diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 2d24c1ea42fa6901f46b2a26a42fca99b5b843f8..43fd628980ff578ca7e66ff7c76147d429b41599 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -208,9 +208,22 @@ public abstract class DisplayList { * {@link #isValid()} will return false. * * @see #isValid() + * @see #reset() */ public abstract void clear(); + + /** + * Reset native resources. This is called when cleaning up the state of display lists + * during destruction of hardware resources, to ensure that we do not hold onto + * obsolete resources after related resources are gone. + * + * @see #clear() + * + * @hide + */ + public abstract void reset(); + /** * Sets the dirty flag. When a display list is dirty, {@link #clear()} should * be invoked whenever possible. @@ -670,13 +683,4 @@ public abstract class DisplayList { * @see View#offsetTopAndBottom(int) */ public abstract void offsetTopAndBottom(float offset); - - /** - * Reset native resources. This is called when cleaning up the state of display lists - * during destruction of hardware resources, to ensure that we do not hold onto - * obsolete resources after related resources are gone. - * - * @hide - */ - public abstract void reset(); } diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 2ec9a7da8655c5b2bf673c725d8fcfdaf3442462..beefc21faf8448c0955201582562dae8c3480646 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -21,6 +21,7 @@ import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.DrawFilter; import android.graphics.Matrix; +import android.graphics.NinePatch; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; @@ -58,11 +59,11 @@ class GLES20Canvas extends HardwareCanvas { private int mWidth; private int mHeight; - private final float[] mPoint = new float[2]; - private final float[] mLine = new float[4]; + private float[] mPoint; + private float[] mLine; - private final Rect mClipBounds = new Rect(); - private final RectF mPathBounds = new RectF(); + private Rect mClipBounds; + private RectF mPathBounds; private DrawFilter mFilter; @@ -161,6 +162,16 @@ class GLES20Canvas extends HardwareCanvas { nPushLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer); } + @Override + void cancelLayerUpdate(HardwareLayer layer) { + nCancelLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer); + } + + @Override + void flushLayerUpdates() { + nFlushLayerUpdates(mRenderer); + } + @Override void clearLayerUpdates() { nClearLayerUpdates(mRenderer); @@ -183,7 +194,9 @@ class GLES20Canvas extends HardwareCanvas { static native boolean nCopyLayer(int layerId, int bitmap); private static native void nClearLayerUpdates(int renderer); + private static native void nFlushLayerUpdates(int renderer); private static native void nPushLayerUpdate(int renderer, int layer); + private static native void nCancelLayerUpdate(int renderer, int layer); /////////////////////////////////////////////////////////////////////////// // Canvas management @@ -273,6 +286,18 @@ class GLES20Canvas extends HardwareCanvas { private static native int nGetStencilSize(); + void setCountOverdrawEnabled(boolean enabled) { + nSetCountOverdrawEnabled(mRenderer, enabled); + } + + static native void nSetCountOverdrawEnabled(int renderer, boolean enabled); + + float getOverdraw() { + return nGetOverdraw(mRenderer); + } + + static native float nGetOverdraw(int renderer); + /////////////////////////////////////////////////////////////////////////// // Functor /////////////////////////////////////////////////////////////////////////// @@ -314,21 +339,21 @@ class GLES20Canvas extends HardwareCanvas { * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_LAYERS = 0; + static final int FLUSH_CACHES_LAYERS = 0; /** * Must match Caches::FlushMode values * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_MODERATE = 1; + static final int FLUSH_CACHES_MODERATE = 1; /** * Must match Caches::FlushMode values * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_FULL = 2; + static final int FLUSH_CACHES_FULL = 2; /** * Flush caches to reclaim as much memory as possible. The amount of memory @@ -338,10 +363,8 @@ class GLES20Canvas extends HardwareCanvas { * {@link #FLUSH_CACHES_FULL}. * * @param level Hint about the amount of memory to reclaim - * - * @hide */ - public static void flushCaches(int level) { + static void flushCaches(int level) { nFlushCaches(level); } @@ -353,21 +376,28 @@ class GLES20Canvas extends HardwareCanvas { * * @hide */ - public static void terminateCaches() { + static void terminateCaches() { nTerminateCaches(); } private static native void nTerminateCaches(); - /** - * @hide - */ - public static void initCaches() { - nInitCaches(); + static boolean initCaches() { + return nInitCaches(); } - private static native void nInitCaches(); - + private static native boolean nInitCaches(); + + /////////////////////////////////////////////////////////////////////////// + // Atlas + /////////////////////////////////////////////////////////////////////////// + + static void initAtlas(GraphicBuffer buffer, int[] map) { + nInitAtlas(buffer, map, map.length); + } + + private static native void nInitAtlas(GraphicBuffer buffer, int[] map, int count); + /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// @@ -418,6 +448,31 @@ class GLES20Canvas extends HardwareCanvas { private static native void nInterrupt(int renderer); private static native void nResume(int renderer); + /////////////////////////////////////////////////////////////////////////// + // Support + /////////////////////////////////////////////////////////////////////////// + + private Rect getInternalClipBounds() { + if (mClipBounds == null) mClipBounds = new Rect(); + return mClipBounds; + } + + + private RectF getPathBounds() { + if (mPathBounds == null) mPathBounds = new RectF(); + return mPathBounds; + } + + private float[] getPointStorage() { + if (mPoint == null) mPoint = new float[2]; + return mPoint; + } + + private float[] getLineStorage() { + if (mLine == null) mLine = new float[4]; + return mLine; + } + /////////////////////////////////////////////////////////////////////////// // Clipping /////////////////////////////////////////////////////////////////////////// @@ -506,9 +561,10 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean quickReject(Path path, EdgeType type) { - path.computeBounds(mPathBounds, true); - return nQuickReject(mRenderer, mPathBounds.left, mPathBounds.top, - mPathBounds.right, mPathBounds.bottom); + RectF pathBounds = getPathBounds(); + path.computeBounds(pathBounds, true); + return nQuickReject(mRenderer, pathBounds.left, pathBounds.top, + pathBounds.right, pathBounds.bottom); } @Override @@ -565,7 +621,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void concat(Matrix matrix) { - nConcatMatrix(mRenderer, matrix.native_instance); + if (matrix != null) nConcatMatrix(mRenderer, matrix.native_instance); } private static native void nConcatMatrix(int renderer, int matrix); @@ -718,25 +774,41 @@ class GLES20Canvas extends HardwareCanvas { } @Override - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + public void drawPatch(NinePatch patch, Rect dst, Paint paint) { + Bitmap bitmap = patch.getBitmap(); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing patches int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks, + nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } finally { if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); } } - private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks, + @Override + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + Bitmap bitmap = patch.getBitmap(); + throwIfCannotDraw(bitmap); + // Shaders are ignored when drawing patches + int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; + try { + final int nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, + dst.left, dst.top, dst.right, dst.bottom, nativePaint); + } finally { + if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); + } + } + + private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, int chunk, float left, float top, float right, float bottom, int paint); @Override public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -747,12 +819,12 @@ class GLES20Canvas extends HardwareCanvas { } } - private static native void nDrawBitmap( - int renderer, int bitmap, byte[] buffer, float left, float top, int paint); + private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer, + float left, float top, int paint); @Override public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -764,12 +836,12 @@ class GLES20Canvas extends HardwareCanvas { } } - private static native void nDrawBitmap(int renderer, int bitmap, byte[] buff, + private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer, int matrix, int paint); @Override public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -796,7 +868,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -872,7 +944,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); if (meshWidth < 0 || meshHeight < 0 || vertOffset < 0 || colorOffset < 0) { throw new ArrayIndexOutOfBoundsException(); } @@ -890,7 +962,7 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { - final int nativePaint = paint == null ? 0 : paint.mNativePaint; + final int nativePaint = paint == null ? 0 : paint.mNativePaint; nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, nativePaint); } finally { @@ -929,11 +1001,12 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - mLine[0] = startX; - mLine[1] = startY; - mLine[2] = stopX; - mLine[3] = stopY; - drawLines(mLine, 0, 4, paint); + float[] line = getLineStorage(); + line[0] = startX; + line[1] = startY; + line[2] = stopX; + line[3] = stopY; + drawLines(line, 0, 4, paint); } @Override @@ -974,7 +1047,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPaint(Paint paint) { - final Rect r = mClipBounds; + final Rect r = getInternalClipBounds(); nGetClipBounds(mRenderer, r); drawRect(r.left, r.top, r.right, r.bottom, paint); } @@ -1051,9 +1124,10 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPoint(float x, float y, Paint paint) { - mPoint[0] = x; - mPoint[1] = y; - drawPoints(mPoint, 0, 2, paint); + float[] point = getPointStorage(); + point[0] = x; + point[1] = y; + drawPoints(point, 0, 2, paint); } @Override diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 3272504fbfa68d35c9cdc740a17254ee427a6240..c652bac1f772ceeada3c8cf1d6e501a4906a3030 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -16,7 +16,6 @@ package android.view; -import android.graphics.Bitmap; import android.graphics.Matrix; import java.util.ArrayList; @@ -25,12 +24,7 @@ import java.util.ArrayList; * An implementation of display list for OpenGL ES 2.0. */ class GLES20DisplayList extends DisplayList { - // These lists ensure that any Bitmaps and DisplayLists recorded by a DisplayList are kept - // alive as long as the DisplayList is alive. The Bitmap and DisplayList lists - // are populated by the GLES20RecordingCanvas during appropriate drawing calls and are - // cleared at the start of a new drawing frame or when the view is detached from the window. - final ArrayList mBitmaps = new ArrayList(5); - final ArrayList mChildDisplayLists = new ArrayList(); + private ArrayList mChildDisplayLists; private GLES20RecordingCanvas mCanvas; private boolean mValid; @@ -83,8 +77,16 @@ class GLES20DisplayList extends DisplayList { } mValid = false; - mBitmaps.clear(); - mChildDisplayLists.clear(); + clearReferences(); + } + + void clearReferences() { + if (mChildDisplayLists != null) mChildDisplayLists.clear(); + } + + ArrayList getChildDisplayLists() { + if (mChildDisplayLists == null) mChildDisplayLists = new ArrayList(); + return mChildDisplayLists; } @Override @@ -92,6 +94,7 @@ class GLES20DisplayList extends DisplayList { if (hasNativeDisplayList()) { nReset(mFinalizer.mNativeDisplayList); } + clear(); } @Override diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java index 7ee628be263d48266277983d464b744b792cf92e..0e3311c1c860c84f52a1839c89d9312cc5d802fd 100644 --- a/core/java/android/view/GLES20Layer.java +++ b/core/java/android/view/GLES20Layer.java @@ -59,6 +59,9 @@ abstract class GLES20Layer extends HardwareLayer { @Override public void destroy() { + if (mDisplayList != null) { + mDisplayList.reset(); + } if (mFinalizer != null) { mFinalizer.destroy(); mFinalizer = null; diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index 7da2451579490cf3367a9ccfca350d74181c588c..b6fc38d892f5eeb1acb1b2c7bd5f9bd81e0ac204 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -16,14 +16,7 @@ package android.view; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Path; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; import android.util.Pools.SynchronizedPool; /** @@ -62,229 +55,17 @@ class GLES20RecordingCanvas extends GLES20Canvas { } void start() { - mDisplayList.mBitmaps.clear(); - mDisplayList.mChildDisplayLists.clear(); + mDisplayList.clearReferences(); } int end(int nativeDisplayList) { return getDisplayList(nativeDisplayList); } - private void recordShaderBitmap(Paint paint) { - if (paint != null) { - final Shader shader = paint.getShader(); - if (shader instanceof BitmapShader) { - mDisplayList.mBitmaps.add(((BitmapShader) shader).mBitmap); - } - } - } - - @Override - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { - super.drawPatch(bitmap, chunks, dst, paint); - mDisplayList.mBitmaps.add(bitmap); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - super.drawBitmap(bitmap, left, top, paint); - mDisplayList.mBitmaps.add(bitmap); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - super.drawBitmap(bitmap, matrix, paint); - mDisplayList.mBitmaps.add(bitmap); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { - super.drawBitmap(bitmap, src, dst, paint); - mDisplayList.mBitmaps.add(bitmap); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { - super.drawBitmap(bitmap, src, dst, paint); - mDisplayList.mBitmaps.add(bitmap); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, - int height, boolean hasAlpha, Paint paint) { - super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, - int height, boolean hasAlpha, Paint paint) { - super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, - int vertOffset, int[] colors, int colorOffset, Paint paint) { - super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, - colors, colorOffset, paint); - mDisplayList.mBitmaps.add(bitmap); - // Shaders in the Paint are ignored when drawing a Bitmap - } - - @Override - public void drawCircle(float cx, float cy, float radius, Paint paint) { - super.drawCircle(cx, cy, radius, paint); - recordShaderBitmap(paint); - } - @Override public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) { int status = super.drawDisplayList(displayList, dirty, flags); - mDisplayList.mChildDisplayLists.add(displayList); + mDisplayList.getChildDisplayLists().add(displayList); return status; } - - @Override - public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - super.drawLine(startX, startY, stopX, stopY, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawLines(float[] pts, int offset, int count, Paint paint) { - super.drawLines(pts, offset, count, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawLines(float[] pts, Paint paint) { - super.drawLines(pts, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawOval(RectF oval, Paint paint) { - super.drawOval(oval, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawPaint(Paint paint) { - super.drawPaint(paint); - recordShaderBitmap(paint); - } - - @Override - public void drawPath(Path path, Paint paint) { - super.drawPath(path, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawPoint(float x, float y, Paint paint) { - super.drawPoint(x, y, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawPoints(float[] pts, int offset, int count, Paint paint) { - super.drawPoints(pts, offset, count, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawPoints(float[] pts, Paint paint) { - super.drawPoints(pts, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { - super.drawPosText(text, index, count, pos, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawPosText(String text, float[] pos, Paint paint) { - super.drawPosText(text, pos, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawRect(float left, float top, float right, float bottom, Paint paint) { - super.drawRect(left, top, right, bottom, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { - super.drawRoundRect(rect, rx, ry, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { - super.drawText(text, index, count, x, y, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { - super.drawText(text, start, end, x, y, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawText(String text, int start, int end, float x, float y, Paint paint) { - super.drawText(text, start, end, x, y, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawText(String text, float x, float y, Paint paint) { - super.drawText(text, x, y, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, - float vOffset, Paint paint) { - super.drawTextOnPath(text, index, count, path, hOffset, vOffset, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { - super.drawTextOnPath(text, path, hOffset, vOffset, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, - float x, float y, int dir, Paint paint) { - super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, dir, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawTextRun(CharSequence text, int start, int end, int contextStart, - int contextEnd, float x, float y, int dir, Paint paint) { - super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, dir, paint); - recordShaderBitmap(paint); - } - - @Override - public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, - float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, - int indexOffset, int indexCount, Paint paint) { - super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset, colors, - colorOffset, indices, indexOffset, indexCount, paint); - recordShaderBitmap(paint); - } } diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java index 685dc7086e1e574f779a2f35bd73dedf9ac3fa18..68ba77c6425750065f148bb8b41005c21a891278 100644 --- a/core/java/android/view/GLES20RenderLayer.java +++ b/core/java/android/view/GLES20RenderLayer.java @@ -100,12 +100,17 @@ class GLES20RenderLayer extends GLES20Layer { @Override HardwareCanvas start(Canvas currentCanvas) { + return start(currentCanvas, null); + } + + @Override + HardwareCanvas start(Canvas currentCanvas, Rect dirty) { if (currentCanvas instanceof GLES20Canvas) { ((GLES20Canvas) currentCanvas).interrupt(); } HardwareCanvas canvas = getCanvas(); canvas.setViewport(mWidth, mHeight); - canvas.onPreDraw(null); + canvas.onPreDraw(dirty); return canvas; } diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java index e863e49a01411d166676586ccabb3bcf19e3aaf5..bb5a6eb324bb4afc3567d33987cc85afcac3d9db 100644 --- a/core/java/android/view/GLES20TextureLayer.java +++ b/core/java/android/view/GLES20TextureLayer.java @@ -62,13 +62,18 @@ class GLES20TextureLayer extends GLES20Layer { return null; } + @Override + HardwareCanvas start(Canvas currentCanvas, Rect dirty) { + return null; + } + @Override void end(Canvas currentCanvas) { } SurfaceTexture getSurfaceTexture() { if (mSurface == null) { - mSurface = new SurfaceTexture(mTexture, false); + mSurface = new SurfaceTexture(mTexture); } return mSurface; } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 28c1058fa90394ac0232bec956299f2f8904ac7e..2351548df75a4115011fdb055126502dc7c6260e 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -17,7 +17,6 @@ package android.view; import android.content.Context; -import android.os.Build; import android.os.Handler; import android.os.Message; @@ -202,6 +201,7 @@ public class GestureDetector { private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); + private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime(); // constants for Message.what used by GestureHandler below private static final int SHOW_PRESS = 1; @@ -323,7 +323,7 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener. - * You may only use this constructor from a UI thread (this is the usual situation). + * You may only use this constructor from a {@link android.os.Looper} thread. * @see android.os.Handler#Handler() * * @param context the application's context @@ -337,14 +337,14 @@ public class GestureDetector { } /** - * Creates a GestureDetector with the supplied listener. - * You may only use this constructor from a UI thread (this is the usual situation). + * Creates a GestureDetector with the supplied listener that runs deferred events on the + * thread associated with the supplied {@link android.os.Handler}. * @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 + * @param handler the handler to use for running deferred listener events. * * @throws NullPointerException if {@code listener} is null. */ @@ -362,14 +362,15 @@ public class GestureDetector { } /** - * Creates a GestureDetector with the supplied listener. - * You may only use this constructor from a UI thread (this is the usual situation). + * Creates a GestureDetector with the supplied listener that runs deferred events on the + * thread associated with the supplied {@link android.os.Handler}. * @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 + * @param handler the handler to use for running deferred listener events. + * @param unused currently not used. * * @throws NullPointerException if {@code listener} is null. */ @@ -672,7 +673,8 @@ public class GestureDetector { return false; } - if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) { + final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime(); + if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) { return false; } diff --git a/core/java/android/view/GraphicBuffer.aidl b/core/java/android/view/GraphicBuffer.aidl new file mode 100644 index 0000000000000000000000000000000000000000..6dc6bede0dee21c72c461604ec926b74224481c9 --- /dev/null +++ b/core/java/android/view/GraphicBuffer.aidl @@ -0,0 +1,19 @@ +/* + * 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.view; + +parcelable GraphicBuffer; diff --git a/core/java/android/view/GraphicBuffer.java b/core/java/android/view/GraphicBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..30c077c2e879bd76f7fb8dc0f7854d3069107749 --- /dev/null +++ b/core/java/android/view/GraphicBuffer.java @@ -0,0 +1,292 @@ +/* + * 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.view; + +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Simple wrapper for the native GraphicBuffer class. + * + * @hide + */ +@SuppressWarnings("UnusedDeclaration") +public class GraphicBuffer implements Parcelable { + // Note: keep usage flags in sync with GraphicBuffer.h and gralloc.h + public static final int USAGE_SW_READ_NEVER = 0x0; + public static final int USAGE_SW_READ_RARELY = 0x2; + public static final int USAGE_SW_READ_OFTEN = 0x3; + public static final int USAGE_SW_READ_MASK = 0xF; + + public static final int USAGE_SW_WRITE_NEVER = 0x0; + public static final int USAGE_SW_WRITE_RARELY = 0x20; + public static final int USAGE_SW_WRITE_OFTEN = 0x30; + public static final int USAGE_SW_WRITE_MASK = 0xF0; + + public static final int USAGE_SOFTWARE_MASK = USAGE_SW_READ_MASK | USAGE_SW_WRITE_MASK; + + public static final int USAGE_PROTECTED = 0x4000; + + public static final int USAGE_HW_TEXTURE = 0x100; + public static final int USAGE_HW_RENDER = 0x200; + public static final int USAGE_HW_2D = 0x400; + public static final int USAGE_HW_COMPOSER = 0x800; + public static final int USAGE_HW_VIDEO_ENCODER = 0x10000; + public static final int USAGE_HW_MASK = 0x71F00; + + private final int mWidth; + private final int mHeight; + private final int mFormat; + private final int mUsage; + // Note: do not rename, this field is used by native code + private final int mNativeObject; + + // These two fields are only used by lock/unlockCanvas() + private Canvas mCanvas; + private int mSaveCount; + + // If set to true, this GraphicBuffer instance cannot be used anymore + private boolean mDestroyed; + + /** + * Creates new GraphicBuffer instance. This method will return null + * if the buffer cannot be created. + * + * @param width The width in pixels of the buffer + * @param height The height in pixels of the buffer + * @param format The format of each pixel as specified in {@link PixelFormat} + * @param usage Hint indicating how the buffer will be used + * + * @return A GraphicBuffer instance or null + */ + public static GraphicBuffer create(int width, int height, int format, int usage) { + int nativeObject = nCreateGraphicBuffer(width, height, format, usage); + if (nativeObject != 0) { + return new GraphicBuffer(width, height, format, usage, nativeObject); + } + return null; + } + + /** + * Private use only. See {@link #create(int, int, int, int)}. + */ + private GraphicBuffer(int width, int height, int format, int usage, int nativeObject) { + mWidth = width; + mHeight = height; + mFormat = format; + mUsage = usage; + mNativeObject = nativeObject; + } + + /** + * Returns the width of this buffer in pixels. + */ + public int getWidth() { + return mWidth; + } + + /** + * Returns the height of this buffer in pixels. + */ + public int getHeight() { + return mHeight; + } + + /** + * Returns the pixel format of this buffer. The pixel format must be one of + * the formats defined in {@link PixelFormat}. + */ + public int getFormat() { + return mFormat; + } + + /** + * Returns the usage hint set on this buffer. + */ + public int getUsage() { + return mUsage; + } + + /** + *

    Start editing the pixels in the buffer. A null is returned if the buffer + * cannot be locked for editing.

    + * + *

    The content of the buffer is preserved between unlockCanvas() + * and lockCanvas().

    + * + *

    If this method is called after {@link #destroy()}, the return value will + * always be null.

    + * + * @return A Canvas used to draw into the buffer, or null. + * + * @see #lockCanvas(android.graphics.Rect) + * @see #unlockCanvasAndPost(android.graphics.Canvas) + * @see #isDestroyed() + */ + public Canvas lockCanvas() { + return lockCanvas(null); + } + + /** + * Just like {@link #lockCanvas()} but allows specification of a dirty + * rectangle. + * + *

    If this method is called after {@link #destroy()}, the return value will + * always be null.

    + * + * @param dirty Area of the buffer that may be modified. + + * @return A Canvas used to draw into the surface, or null. + * + * @see #lockCanvas() + * @see #unlockCanvasAndPost(android.graphics.Canvas) + * @see #isDestroyed() + */ + public Canvas lockCanvas(Rect dirty) { + if (mDestroyed) { + return null; + } + + if (mCanvas == null) { + mCanvas = new Canvas(); + } + + if (nLockCanvas(mNativeObject, mCanvas, dirty)) { + mSaveCount = mCanvas.save(); + return mCanvas; + } + + return null; + } + + /** + * Finish editing pixels in the buffer. + * + *

    This method doesn't do anything if {@link #destroy()} was + * previously called.

    + * + * @param canvas The Canvas previously returned by lockCanvas() + * + * @see #lockCanvas() + * @see #lockCanvas(android.graphics.Rect) + * @see #isDestroyed() + */ + public void unlockCanvasAndPost(Canvas canvas) { + if (!mDestroyed && mCanvas != null && canvas == mCanvas) { + canvas.restoreToCount(mSaveCount); + mSaveCount = 0; + + nUnlockCanvasAndPost(mNativeObject, mCanvas); + } + } + + /** + * Destroyes this buffer immediately. Calling this method frees up any + * underlying native resources. After calling this method, this buffer + * must not be used in any way ({@link #lockCanvas()} must not be called, + * etc.) + * + * @see #isDestroyed() + */ + public void destroy() { + if (!mDestroyed) { + mDestroyed = true; + nDestroyGraphicBuffer(mNativeObject); + } + } + + /** + * Indicates whether this buffer has been destroyed. A destroyed buffer + * cannot be used in any way: locking a Canvas will return null, the buffer + * cannot be written to a parcel, etc. + * + * @return True if this GraphicBuffer is in a destroyed state, + * false otherwise. + * + * @see #destroy() + */ + public boolean isDestroyed() { + return mDestroyed; + } + + @Override + protected void finalize() throws Throwable { + try { + if (!mDestroyed) nDestroyGraphicBuffer(mNativeObject); + } finally { + super.finalize(); + } + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Flatten this object in to a Parcel. + * + *

    Calling this method will throw an IllegalStateException if + * {@link #destroy()} has been previously called.

    + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mDestroyed) { + throw new IllegalStateException("This GraphicBuffer has been destroyed and cannot be " + + "written to a parcel."); + } + + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeInt(mFormat); + dest.writeInt(mUsage); + nWriteGraphicBufferToParcel(mNativeObject, dest); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public GraphicBuffer createFromParcel(Parcel in) { + int width = in.readInt(); + int height = in.readInt(); + int format = in.readInt(); + int usage = in.readInt(); + int nativeObject = nReadGraphicBufferFromParcel(in); + if (nativeObject != 0) { + return new GraphicBuffer(width, height, format, usage, nativeObject); + } + return null; + } + + public GraphicBuffer[] newArray(int size) { + return new GraphicBuffer[size]; + } + }; + + private static native int nCreateGraphicBuffer(int width, int height, int format, int usage); + private static native void nDestroyGraphicBuffer(int nativeObject); + private static native void nWriteGraphicBufferToParcel(int nativeObject, Parcel dest); + private static native int nReadGraphicBufferFromParcel(Parcel in); + private static native boolean nLockCanvas(int nativeObject, Canvas canvas, Rect dirty); + private static native boolean nUnlockCanvasAndPost(int nativeObject, Canvas canvas); +} diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 0dfed69934a457e2325b63d6ade3ee5d26f4adf8..259e1cd02f44c405c7c3c954d25ef62c2fb87c48 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -201,6 +201,28 @@ public abstract class HardwareCanvas extends Canvas { */ abstract void pushLayerUpdate(HardwareLayer layer); + /** + * Cancels a queued layer update. If the specified layer was not + * queued for update, this method has no effect. + * + * @param layer The layer whose update to cancel + * + * @see #pushLayerUpdate(HardwareLayer) + * @see #clearLayerUpdates() + * + * @hide + */ + abstract void cancelLayerUpdate(HardwareLayer layer); + + /** + * Immediately executes all enqueued layer updates. + * + * @see #pushLayerUpdate(HardwareLayer) + * + * @hide + */ + abstract void flushLayerUpdates(); + /** * Removes all enqueued layer updates. * diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 18b838b37aa7b67ef531379673013b56ce93ba8c..23383d9c0473651d6ac6283308c3096c154a0477 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -158,14 +158,22 @@ abstract class HardwareLayer { /** * This must be invoked before drawing onto this layer. * - * @param currentCanvas + * @param currentCanvas The canvas whose rendering needs to be interrupted */ abstract HardwareCanvas start(Canvas currentCanvas); + /** + * This must be invoked before drawing onto this layer. + * + * @param dirty The dirty area to repaint + * @param currentCanvas The canvas whose rendering needs to be interrupted + */ + abstract HardwareCanvas start(Canvas currentCanvas, Rect dirty); + /** * This must be invoked after drawing onto this layer. * - * @param currentCanvas + * @param currentCanvas The canvas whose rendering needs to be resumed */ abstract void end(Canvas currentCanvas); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 83084591f1c5920ec0aa48a5da78146f2db5b822..f2151893139077b15ade2bc3642d44c433abe754 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -17,6 +17,7 @@ package android.view; import android.content.ComponentCallbacks2; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; @@ -24,12 +25,17 @@ import android.opengl.EGL14; import android.opengl.GLUtils; import android.opengl.ManagedEGLContext; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Surface.OutOfResourcesException; + import com.google.android.gles_jni.EGLImpl; import javax.microedition.khronos.egl.EGL10; @@ -42,7 +48,6 @@ import javax.microedition.khronos.opengles.GL; import java.io.File; import java.io.PrintWriter; -import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; import static javax.microedition.khronos.egl.EGL10.*; @@ -71,7 +76,7 @@ public abstract class HardwareRenderer { * System property used to enable or disable dirty regions invalidation. * This property is only queried if {@link #RENDER_DIRTY_REGIONS} is true. * The default value of this property is assumed to be true. - * + * * Possible values: * "true", to enable partial invalidates * "false", to disable partial invalidates @@ -131,7 +136,7 @@ public abstract class HardwareRenderer { /** * System property used to debug EGL configuration choice. - * + * * Possible values: * "choice", print the chosen configuration only * "all", print all possible configurations @@ -144,7 +149,7 @@ public abstract class HardwareRenderer { * Possible values: * "true", to enable dirty regions debugging * "false", to disable dirty regions debugging - * + * * @hide */ public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions"; @@ -162,15 +167,32 @@ public abstract class HardwareRenderer { "debug.hwui.show_layers_updates"; /** - * Turn on to show overdraw level. + * Controls overdraw debugging. * * Possible values: - * "true", to enable overdraw debugging * "false", to disable overdraw debugging + * "show", to show overdraw areas on screen + * "count", to display an overdraw counter + * + * @hide + */ + public static final String DEBUG_OVERDRAW_PROPERTY = "debug.hwui.overdraw"; + + /** + * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this + * value, overdraw will be shown on screen by coloring pixels. + * + * @hide + */ + public static final String OVERDRAW_PROPERTY_SHOW = "show"; + + /** + * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this + * value, an overdraw counter will be shown on screen. * * @hide */ - public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw"; + public static final String OVERDRAW_PROPERTY_COUNT = "count"; /** * Turn on to debug non-rectangular clip operations. @@ -188,14 +210,14 @@ public abstract class HardwareRenderer { /** * A process can set this flag to false to prevent the use of hardware * rendering. - * + * * @hide */ public static boolean sRendererDisabled = false; /** * Further hardware renderer disabling for the system process. - * + * * @hide */ public static boolean sSystemRendererDisabled = false; @@ -215,7 +237,7 @@ public abstract class HardwareRenderer { /** * Invoke this method to disable hardware rendering in the current process. - * + * * @hide */ public static void disable(boolean system) { @@ -228,7 +250,7 @@ public abstract class HardwareRenderer { /** * Indicates whether hardware acceleration is available under any form for * the view hierarchy. - * + * * @return True if the view hierarchy can potentially be hardware accelerated, * false otherwise */ @@ -238,30 +260,30 @@ public abstract class HardwareRenderer { /** * Destroys the hardware rendering context. - * + * * @param full If true, destroys all associated resources. */ abstract void destroy(boolean full); /** * Initializes the hardware renderer for the specified surface. - * + * * @param surface The surface to hardware accelerate - * + * * @return True if the initialization was successful, false otherwise. */ - abstract boolean initialize(Surface surface) throws Surface.OutOfResourcesException; - + abstract boolean initialize(Surface surface) throws OutOfResourcesException; + /** * Updates the hardware renderer for the specified surface. * * @param surface The surface to hardware accelerate */ - abstract void updateSurface(Surface surface) throws Surface.OutOfResourcesException; + abstract void updateSurface(Surface surface) throws OutOfResourcesException; /** * Destroys the layers used by the specified view hierarchy. - * + * * @param view The root of the view hierarchy */ abstract void destroyLayers(View view); @@ -269,11 +291,11 @@ public abstract class HardwareRenderer { /** * Destroys all hardware rendering resources associated with the specified * view hierarchy. - * + * * @param view The root of the view hierarchy */ abstract void destroyHardwareResources(View view); - + /** * This method should be invoked whenever the current hardware renderer * context should be reset. @@ -286,7 +308,7 @@ public abstract class HardwareRenderer { * This method should be invoked to ensure the hardware renderer is in * valid state (for instance, to ensure the correct EGL context is bound * to the current thread.) - * + * * @return true if the renderer is now valid, false otherwise */ abstract boolean validate(); @@ -294,7 +316,7 @@ public abstract class HardwareRenderer { /** * This method ensures the hardware renderer is in a valid state * before executing the specified action. - * + * * This method will attempt to set a valid state even if the window * the renderer is attached to was destroyed. * @@ -305,7 +327,7 @@ public abstract class HardwareRenderer { /** * Setup the hardware renderer for drawing. This is called whenever the * size of the target surface changes or when the surface is first created. - * + * * @param width Width of the drawing surface. * @param height Height of the drawing surface. */ @@ -364,7 +386,7 @@ public abstract class HardwareRenderer { /** * Sets the directory to use as a persistent storage for hardware rendering * resources. - * + * * @param cacheDir A directory the current process can write to * * @hide @@ -385,6 +407,17 @@ public abstract class HardwareRenderer { private static native void nBeginFrame(int[] size); + /** + * Returns the current system time according to the renderer. + * This method is used for debugging only and should not be used + * as a clock. + */ + static long getSystemTime() { + return nGetSystemTime(); + } + + private static native long nGetSystemTime(); + /** * Preserves the back buffer of the current surface after a buffer swap. * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current @@ -416,11 +449,31 @@ public abstract class HardwareRenderer { /** * Indicates that the specified hardware layer needs to be updated * as soon as possible. - * + * * @param layer The hardware layer that needs an update + * + * @see #flushLayerUpdates() + * @see #cancelLayerUpdate(HardwareLayer) */ abstract void pushLayerUpdate(HardwareLayer layer); + /** + * Cancels a queued layer update. If the specified layer was not + * queued for update, this method has no effect. + * + * @param layer The layer whose update to cancel + * + * @see #pushLayerUpdate(HardwareLayer) + */ + abstract void cancelLayerUpdate(HardwareLayer layer); + + /** + * Forces all enqueued layer updates to be executed immediately. + * + * @see #pushLayerUpdate(HardwareLayer) + */ + abstract void flushLayerUpdates(); + /** * Interface used to receive callbacks whenever a view is drawn by * a hardware renderer instance. @@ -430,7 +483,7 @@ public abstract class HardwareRenderer { * Invoked before a view is drawn by a hardware renderer. * This method can be used to apply transformations to the * canvas but no drawing command should be issued. - * + * * @param canvas The Canvas used to render the view. */ void onHardwarePreDraw(HardwareCanvas canvas); @@ -438,7 +491,7 @@ public abstract class HardwareRenderer { /** * Invoked after a view is drawn by a hardware renderer. * It is safe to invoke drawing commands from this method. - * + * * @param canvas The Canvas used to render the view. */ void onHardwarePostDraw(HardwareCanvas canvas); @@ -458,9 +511,9 @@ public abstract class HardwareRenderer { /** * Creates a new display list that can be used to record batches of * drawing operations. - * + * * @param name The name of the display list, used for debugging purpose. May be null. - * + * * @return A new display list. * * @hide @@ -470,20 +523,20 @@ public abstract class HardwareRenderer { /** * Creates a new hardware layer. A hardware layer built by calling this * method will be treated as a texture layer, instead of as a render target. - * + * * @param isOpaque Whether the layer should be opaque or not - * + * * @return A hardware layer */ abstract HardwareLayer createHardwareLayer(boolean isOpaque); /** * Creates a new hardware layer. - * + * * @param width The minimum width of the layer * @param height The minimum height of the layer * @param isOpaque Whether the layer should be opaque or not - * + * * @return A hardware layer */ abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque); @@ -493,7 +546,7 @@ public abstract class HardwareRenderer { * specified hardware layer. * * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture} - * + * * @return A {@link SurfaceTexture} */ abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer); @@ -509,11 +562,11 @@ public abstract class HardwareRenderer { /** * Detaches the specified functor from the current functor execution queue. - * + * * @param functor The native functor to remove from the execution queue. - * - * @see HardwareCanvas#callDrawGLFunction(int) - * @see #attachFunctor(android.view.View.AttachInfo, int) + * + * @see HardwareCanvas#callDrawGLFunction(int) + * @see #attachFunctor(android.view.View.AttachInfo, int) */ abstract void detachFunctor(int functor); @@ -540,12 +593,12 @@ public abstract class HardwareRenderer { * @param width The width of the drawing surface. * @param height The height of the drawing surface. * @param surface The surface to hardware accelerate - * + * * @return true if the surface was initialized, false otherwise. Returning * false might mean that the surface was already initialized. */ boolean initializeIfNeeded(int width, int height, Surface surface) - throws Surface.OutOfResourcesException { + throws OutOfResourcesException { if (isRequested()) { // We lost the gl context, so recreate it. if (!isEnabled()) { @@ -567,10 +620,10 @@ public abstract class HardwareRenderer { /** * Creates a hardware renderer using OpenGL. - * + * * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.) * @param translucent True if the surface is translucent, false otherwise - * + * * @return A hardware renderer backed by OpenGL. */ static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) { @@ -585,7 +638,7 @@ public abstract class HardwareRenderer { * Invoke this method when the system is running out of memory. This * method will attempt to recover as much memory as possible, based on * the specified hint. - * + * * @param level Hint about the amount of memory that should be trimmed, * see {@link android.content.ComponentCallbacks} */ @@ -598,7 +651,7 @@ public abstract class HardwareRenderer { * Starts the process of trimming memory. Usually this call will setup * hardware rendering context and reclaim memory.Extra cleanup might * be required by calling {@link #endTrimMemory()}. - * + * * @param level Hint about the amount of memory that should be trimmed, * see {@link android.content.ComponentCallbacks} */ @@ -616,7 +669,7 @@ public abstract class HardwareRenderer { /** * Indicates whether hardware acceleration is currently enabled. - * + * * @return True if hardware acceleration is in use, false otherwise. */ boolean isEnabled() { @@ -625,7 +678,7 @@ public abstract class HardwareRenderer { /** * Indicates whether hardware acceleration is currently enabled. - * + * * @param enabled True if the hardware renderer is in use, false otherwise. */ void setEnabled(boolean enabled) { @@ -635,7 +688,7 @@ public abstract class HardwareRenderer { /** * Indicates whether hardware acceleration is currently request but not * necessarily enabled yet. - * + * * @return True if requested, false otherwise. */ boolean isRequested() { @@ -645,7 +698,7 @@ public abstract class HardwareRenderer { /** * Indicates whether hardware acceleration is currently requested but not * necessarily enabled yet. - * + * * @return True to request hardware acceleration, false otherwise. */ void setRequested(boolean requested) { @@ -762,6 +815,17 @@ public abstract class HardwareRenderer { private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; private static final int PROFILE_DRAW_DP_PER_MS = 7; + private static final String[] VISUALIZERS = { + PROFILE_PROPERTY_VISUALIZE_BARS, + PROFILE_PROPERTY_VISUALIZE_LINES + }; + + private static final String[] OVERDRAW = { + OVERDRAW_PROPERTY_SHOW, + OVERDRAW_PROPERTY_COUNT + }; + private static final int OVERDRAW_TYPE_COUNT = 1; + static EGL10 sEgl; static EGLDisplay sEglDisplay; static EGLConfig sEglConfig; @@ -775,7 +839,7 @@ public abstract class HardwareRenderer { Thread mEglThread; EGLSurface mEglSurface; - + GL mGl; HardwareCanvas mCanvas; @@ -807,7 +871,9 @@ public abstract class HardwareRenderer { Paint mProfilePaint; boolean mDebugDirtyRegions; - boolean mShowOverdraw; + int mDebugOverdraw = -1; + HardwareLayer mDebugOverdrawLayer; + Paint mDebugOverdrawPaint; final int mGlVersion; final boolean mTranslucent; @@ -819,6 +885,8 @@ public abstract class HardwareRenderer { private final int[] mSurfaceSize = new int[2]; private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable(); + private long mDrawDelta = Long.MAX_VALUE; + GlRenderer(int glVersion, boolean translucent) { mGlVersion = glVersion; mTranslucent = translucent; @@ -826,18 +894,13 @@ public abstract class HardwareRenderer { loadSystemProperties(null); } - private static final String[] VISUALIZERS = { - PROFILE_PROPERTY_VISUALIZE_BARS, - PROFILE_PROPERTY_VISUALIZE_LINES - }; - @Override boolean loadSystemProperties(Surface surface) { boolean value; boolean changed = false; String profiling = SystemProperties.get(PROFILE_PROPERTY); - int graphType = Arrays.binarySearch(VISUALIZERS, profiling); + int graphType = search(VISUALIZERS, profiling); value = graphType >= 0; if (graphType != mProfileVisualizerType) { @@ -894,11 +957,19 @@ public abstract class HardwareRenderer { } } - value = SystemProperties.getBoolean( - HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false); - if (value != mShowOverdraw) { + String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY); + int debugOverdraw = search(OVERDRAW, overdraw); + if (debugOverdraw != mDebugOverdraw) { changed = true; - mShowOverdraw = value; + mDebugOverdraw = debugOverdraw; + + if (mDebugOverdraw != OVERDRAW_TYPE_COUNT) { + if (mDebugOverdrawLayer != null) { + mDebugOverdrawLayer.destroy(); + mDebugOverdrawLayer = null; + mDebugOverdrawPaint = null; + } + } } if (nLoadProperties()) { @@ -908,6 +979,13 @@ public abstract class HardwareRenderer { return changed; } + private static int search(String[] values, String value) { + for (int i = 0; i < values.length; i++) { + if (values[i].equals(value)) return i; + } + return -1; + } + @Override void dumpGfxInfo(PrintWriter pw) { if (mProfileEnabled) { @@ -968,15 +1046,15 @@ public abstract class HardwareRenderer { if (fallback) { // we'll try again if it was context lost setRequested(false); - Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + "Switching back to software rendering."); } } @Override - boolean initialize(Surface surface) throws Surface.OutOfResourcesException { + boolean initialize(Surface surface) throws OutOfResourcesException { if (isRequested() && !isEnabled()) { - initializeEgl(); + boolean contextCreated = initializeEgl(); mGl = createEglSurface(surface); mDestroyed = false; @@ -991,6 +1069,10 @@ public abstract class HardwareRenderer { mCanvas.setName(mName); } setEnabled(true); + + if (contextCreated) { + initAtlas(); + } } return mCanvas != null; @@ -998,9 +1080,9 @@ public abstract class HardwareRenderer { } return false; } - + @Override - void updateSurface(Surface surface) throws Surface.OutOfResourcesException { + void updateSurface(Surface surface) throws OutOfResourcesException { if (isRequested() && isEnabled()) { createEglSurface(surface); } @@ -1010,19 +1092,19 @@ public abstract class HardwareRenderer { abstract int[] getConfig(boolean dirtyRegions); - void initializeEgl() { + boolean initializeEgl() { synchronized (sEglLock) { if (sEgl == null && sEglConfig == null) { sEgl = (EGL10) EGLContext.getEGL(); - + // Get to the default display. sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); - + if (sEglDisplay == EGL_NO_DISPLAY) { throw new RuntimeException("eglGetDisplay failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } - + // We can now initialize EGL for that display int[] version = new int[2]; if (!sEgl.eglInitialize(sEglDisplay, version)) { @@ -1043,7 +1125,10 @@ public abstract class HardwareRenderer { if (mEglContext == null) { mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); sEglContextStorage.set(createManagedContext(mEglContext)); + return true; } + + return false; } private EGLConfig loadEglConfig() { @@ -1133,7 +1218,7 @@ public abstract class HardwareRenderer { Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0])); } - GL createEglSurface(Surface surface) throws Surface.OutOfResourcesException { + GL createEglSurface(Surface surface) throws OutOfResourcesException { // Check preconditions. if (sEgl == null) { throw new RuntimeException("egl not initialized"); @@ -1145,7 +1230,7 @@ public abstract class HardwareRenderer { throw new RuntimeException("eglConfig not initialized"); } if (Thread.currentThread() != mEglThread) { - throw new IllegalStateException("HardwareRenderer cannot be used " + throw new IllegalStateException("HardwareRenderer cannot be used " + "from multiple threads"); } @@ -1181,6 +1266,7 @@ public abstract class HardwareRenderer { } abstract void initCaches(); + abstract void initAtlas(); EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; @@ -1193,6 +1279,7 @@ public abstract class HardwareRenderer { "Could not create an EGL context. eglCreateContext failed with error: " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } + return context; } @@ -1216,7 +1303,10 @@ public abstract class HardwareRenderer { void destroySurface() { if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { + sEgl.eglMakeCurrent(sEglDisplay, + EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } sEgl.eglDestroySurface(sEglDisplay, mEglSurface); mEglSurface = null; } @@ -1272,7 +1362,7 @@ public abstract class HardwareRenderer { @Override boolean validate() { - return checkCurrent() != SURFACE_STATE_ERROR; + return checkRenderContext() != SURFACE_STATE_ERROR; } @Override @@ -1306,8 +1396,8 @@ public abstract class HardwareRenderer { boolean canDraw() { return mGl != null && mCanvas != null; - } - + } + int onPreDraw(Rect dirty) { return DisplayList.STATUS_DONE; } @@ -1325,8 +1415,7 @@ public abstract class HardwareRenderer { return; } - final int surfaceState = checkCurrent(); - if (surfaceState != SURFACE_STATE_ERROR) { + if (checkRenderContext() != SURFACE_STATE_ERROR) { int status = mCanvas.invokeFunctors(mRedrawClip); handleFunctorStatus(attachInfo, status); } @@ -1345,7 +1434,8 @@ public abstract class HardwareRenderer { view.mPrivateFlags |= View.PFLAG_DRAWN; - final int surfaceState = checkCurrent(); + // We are already on the correct thread + final int surfaceState = checkRenderContextUnsafe(); if (surfaceState != SURFACE_STATE_ERROR) { HardwareCanvas canvas = mCanvas; attachInfo.mHardwareCanvas = canvas; @@ -1358,9 +1448,17 @@ public abstract class HardwareRenderer { DisplayList displayList = buildDisplayList(view, canvas); + // buildDisplayList() calls into user code which can cause + // an eglMakeCurrent to happen with a different surface/context. + // We must therefore check again here. + if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) { + return; + } + int saveCount = 0; int status = DisplayList.STATUS_DONE; + long start = getSystemTime(); try { status = prepareFrame(dirty); @@ -1380,10 +1478,15 @@ public abstract class HardwareRenderer { canvas.restoreToCount(saveCount); view.mRecreateDisplayList = false; - mFrameCount++; + mDrawDelta = getSystemTime() - start; - debugDirtyRegions(dirty, canvas); - drawProfileData(attachInfo); + if (mDrawDelta > 0) { + mFrameCount++; + + debugOverdraw(attachInfo, dirty, canvas, displayList); + debugDirtyRegions(dirty, canvas); + drawProfileData(attachInfo); + } } onPostDraw(); @@ -1399,7 +1502,63 @@ public abstract class HardwareRenderer { } } + abstract void countOverdraw(HardwareCanvas canvas); + abstract float getOverdraw(HardwareCanvas canvas); + + private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty, + HardwareCanvas canvas, DisplayList displayList) { + + if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) { + if (mDebugOverdrawLayer == null) { + mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true); + } else if (mDebugOverdrawLayer.getWidth() != mWidth || + mDebugOverdrawLayer.getHeight() != mHeight) { + mDebugOverdrawLayer.resize(mWidth, mHeight); + } + + if (!mDebugOverdrawLayer.isValid()) { + mDebugOverdraw = -1; + return; + } + + HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty); + countOverdraw(layerCanvas); + final int restoreCount = layerCanvas.save(); + layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN); + layerCanvas.restoreToCount(restoreCount); + mDebugOverdrawLayer.end(canvas); + + float overdraw = getOverdraw(layerCanvas); + DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics(); + + drawOverdrawCounter(canvas, overdraw, metrics.density); + } + } + + private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) { + final String text = String.format("%.2fx", overdraw); + final Paint paint = setupPaint(density); + // HSBtoColor will clamp the values in the 0..1 range + paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f)); + + canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint); + } + + private Paint setupPaint(float density) { + if (mDebugOverdrawPaint == null) { + mDebugOverdrawPaint = new Paint(); + mDebugOverdrawPaint.setAntiAlias(true); + mDebugOverdrawPaint.setShadowLayer(density * 3.0f, 0.0f, 0.0f, 0xff000000); + mDebugOverdrawPaint.setTextSize(density * 20.0f); + } + return mDebugOverdrawPaint; + } + private DisplayList buildDisplayList(View view, HardwareCanvas canvas) { + if (mDrawDelta <= 0) { + return view.mDisplayList; + } + view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED; view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; @@ -1572,21 +1731,39 @@ public abstract class HardwareRenderer { } /** - * Ensures the current EGL context is the one we expect. - * + * Ensures the current EGL context and surface are the ones we expect. + * This method throws an IllegalStateException if invoked from a thread + * that did not initialize EGL. + * * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one + * + * @see #checkRenderContextUnsafe() */ - int checkCurrent() { + int checkRenderContext() { if (mEglThread != Thread.currentThread()) { throw new IllegalStateException("Hardware acceleration can only be used with a " + "single UI thread.\nOriginal thread: " + mEglThread + "\n" + "Current thread: " + Thread.currentThread()); } - if (!mEglContext.equals(sEgl.eglGetCurrentContext()) || - !mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { + return checkRenderContextUnsafe(); + } + + /** + * Ensures the current EGL context and surface are the ones we expect. + * This method does not check the current thread. + * + * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, + * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or + * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one + * + * @see #checkRenderContext() + */ + private int checkRenderContextUnsafe() { + if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) || + !mEglContext.equals(sEgl.eglGetCurrentContext())) { if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { Log.e(LOG_TAG, "eglMakeCurrent failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); @@ -1788,13 +1965,43 @@ public abstract class HardwareRenderer { @Override void initCaches() { - GLES20Canvas.initCaches(); + if (GLES20Canvas.initCaches()) { + // Caches were (re)initialized, rebind atlas + initAtlas(); + } + } + + @Override + void initAtlas() { + IBinder binder = ServiceManager.getService("assetatlas"); + if (binder == null) return; + + IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); + try { + if (atlas.isCompatible(android.os.Process.myPpid())) { + GraphicBuffer buffer = atlas.getBuffer(); + if (buffer != null) { + int[] map = atlas.getMap(); + if (map != null) { + GLES20Canvas.initAtlas(buffer, map); + } + // If IAssetAtlas is not the same class as the IBinder + // we are using a remote service and we can safely + // destroy the graphic buffer + if (atlas.getClass() != binder.getClass()) { + buffer.destroy(); + } + } + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Could not acquire atlas", e); + } } @Override boolean canDraw() { return super.canDraw() && mGlCanvas != null; - } + } @Override int onPreDraw(Rect dirty) { @@ -1969,6 +2176,16 @@ public abstract class HardwareRenderer { mGlCanvas.pushLayerUpdate(layer); } + @Override + void cancelLayerUpdate(HardwareLayer layer) { + mGlCanvas.cancelLayerUpdate(layer); + } + + @Override + void flushLayerUpdates() { + mGlCanvas.flushLayerUpdates(); + } + @Override public DisplayList createDisplayList(String name) { return new GLES20DisplayList(name); @@ -1984,6 +2201,16 @@ public abstract class HardwareRenderer { return new GLES20RenderLayer(width, height, isOpaque); } + @Override + void countOverdraw(HardwareCanvas canvas) { + ((GLES20Canvas) canvas).setCountOverdrawEnabled(true); + } + + @Override + float getOverdraw(HardwareCanvas canvas) { + return ((GLES20Canvas) canvas).getOverdraw(); + } + @Override public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { return ((GLES20TextureLayer) layer).getSurfaceTexture(); @@ -1996,8 +2223,7 @@ public abstract class HardwareRenderer { @Override boolean safelyRun(Runnable action) { - boolean needsContext = true; - if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false; + boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; if (needsContext) { Gl20RendererEglContext managedContext = diff --git a/core/java/android/view/IApplicationToken.aidl b/core/java/android/view/IApplicationToken.aidl index 5f0600f9e566b9a669d24eb26b00241c6a670b14..633b40fb79679d7828545cc1e66747b352c8b740 100644 --- a/core/java/android/view/IApplicationToken.aidl +++ b/core/java/android/view/IApplicationToken.aidl @@ -23,7 +23,7 @@ interface IApplicationToken void windowsDrawn(); void windowsVisible(); void windowsGone(); - boolean keyDispatchingTimedOut(); + boolean keyDispatchingTimedOut(String reason); long getKeyDispatchingTimeout(); } diff --git a/core/java/android/view/IAssetAtlas.aidl b/core/java/android/view/IAssetAtlas.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1e238923edb339d1cbfcdb8f1d9c8ca8759c64 --- /dev/null +++ b/core/java/android/view/IAssetAtlas.aidl @@ -0,0 +1,54 @@ +/** + * 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.view; + +import android.view.GraphicBuffer; + +/** + * Programming interface to the system assets atlas. This atlas, when + * present, holds preloaded drawable in a single, shareable graphics + * buffer. This allows multiple processes to share the same data to + * save up on memory. + * + * @hide + */ +interface IAssetAtlas { + /** + * Indicates whether the atlas is compatible with the specified + * parent process id. If the atlas' ppid does not match, this + * method will return false. + */ + boolean isCompatible(int ppid); + + /** + * Returns the atlas buffer (texture) or null if the atlas is + * not available yet. + */ + GraphicBuffer getBuffer(); + + /** + * Returns the map of the bitmaps stored in the atlas or null + * if the atlas is not available yet. + * + * Each bitmap is represented by several entries in the array: + * int0: SkBitmap*, the native bitmap object + * int1: x position + * int2: y position + * int3: rotated, 1 if the bitmap must be rotated, 0 otherwise + */ + int[] getMap(); +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8ed4a86e9b60ab64de3a5e3b1297d20491be7b10..9d4af006258e2ea448fe995eed27df7cd65f4458 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -71,17 +71,14 @@ interface IWindowManager void setOverscan(int displayId, int left, int top, int right, int bottom); - // Is the device configured to have a full system bar for larger screens? - boolean hasSystemNavBar(); - // These can only be called when holding the MANAGE_APP_TOKENS permission. void pauseKeyDispatching(IBinder token); void resumeKeyDispatching(IBinder token); void setEventDispatching(boolean enabled); void addWindowToken(IBinder token, int type); void removeWindowToken(IBinder token); - void addAppToken(int addPos, IApplicationToken token, - int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked); + void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId, + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId); void setAppGroupId(IBinder token, int groupId); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); @@ -97,15 +94,12 @@ interface IWindowManager void executeAppTransition(); void setAppStartingWindow(IBinder token, String pkg, int theme, in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, - int icon, int windowFlags, IBinder transferFrom, boolean createIfNeeded); + int icon, int logo, int windowFlags, IBinder transferFrom, boolean createIfNeeded); void setAppWillBeHidden(IBinder token); void setAppVisibility(IBinder token, boolean visible); void startAppFreezingScreen(IBinder token, int configChanges); void stopAppFreezingScreen(IBinder token, boolean force); void removeAppToken(IBinder token); - void moveAppToken(int index, IBinder token); - void moveAppTokensToTop(in List tokens); - void moveAppTokensToBottom(in List tokens); // Re-evaluate the current orientation from the caller's state. // If there is a change, the new Configuration is returned and the @@ -211,7 +205,8 @@ interface IWindowManager /** * Create a screenshot of the applications currently displayed. */ - Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight); + Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, + int maxHeight, boolean force565); /** * Called by the status bar to notify Views of changes to System UI visiblity. @@ -254,12 +249,6 @@ interface IWindowManager */ boolean isSafeModeEnabled(); - /** - * Tell keyguard to show the assistant (Intent.ACTION_ASSIST) after asking for the user's - * credentials. - */ - void showAssistant(); - /** * Sets the display magnification callbacks. These callbacks notify * the client for contextual changes related to display magnification. @@ -285,4 +274,11 @@ interface IWindowManager * @return The magnification spec if such or null. */ MagnificationSpec getCompatibleMagnificationSpecForWindow(in IBinder windowToken); + + /** + * Sets the current touch exploration state. + * + * @param enabled Whether touch exploration is enabled. + */ + void setTouchExplorationEnabled(boolean enabled); } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 2a761c1b380f2d9fe3d35354307e021d7ee491b9..e8291160a7f834e923c14de56a836389e37dbd52 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -44,13 +44,17 @@ import java.util.List; public final class InputDevice implements Parcelable { private final int mId; private final int mGeneration; + private final int mControllerNumber; private final String mName; + private final int mVendorId; + private final int mProductId; private final String mDescriptor; private final boolean mIsExternal; private final int mSources; private final int mKeyboardType; private final KeyCharacterMap mKeyCharacterMap; private final boolean mHasVibrator; + private final boolean mHasButtonUnderPad; private final ArrayList mMotionRanges = new ArrayList(); private Vibrator mVibrator; // guarded by mMotionRanges during initialization @@ -341,30 +345,38 @@ public final class InputDevice implements Parcelable { }; // Called by native code. - private InputDevice(int id, int generation, String name, String descriptor, - boolean isExternal, int sources, - int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator) { + private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, + int productId, String descriptor, boolean isExternal, int sources, int keyboardType, + KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasButtonUnderPad) { mId = id; mGeneration = generation; + mControllerNumber = controllerNumber; mName = name; + mVendorId = vendorId; + mProductId = productId; mDescriptor = descriptor; mIsExternal = isExternal; mSources = sources; mKeyboardType = keyboardType; mKeyCharacterMap = keyCharacterMap; mHasVibrator = hasVibrator; + mHasButtonUnderPad = hasButtonUnderPad; } private InputDevice(Parcel in) { mId = in.readInt(); mGeneration = in.readInt(); + mControllerNumber = in.readInt(); mName = in.readString(); + mVendorId = in.readInt(); + mProductId = in.readInt(); mDescriptor = in.readString(); mIsExternal = in.readInt() != 0; mSources = in.readInt(); mKeyboardType = in.readInt(); mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in); mHasVibrator = in.readInt() != 0; + mHasButtonUnderPad = in.readInt() != 0; for (;;) { int axis = in.readInt(); @@ -409,6 +421,25 @@ public final class InputDevice implements Parcelable { return mId; } + /** + * The controller number for a given input device. + *

    + * Each gamepad or joystick is given a unique, positive controller number when initially + * configured by the system. This number may change due to events such as device disconnects / + * reconnects or user initiated reassignment. Any change in number will trigger an event that + * can be observed by registering an {@link InputManager.InputDeviceListener}. + *

    + *

    + * All input devices which are not gamepads or joysticks will be assigned a controller number + * of 0. + *

    + * + * @return The controller number of the device. + */ + public int getControllerNumber() { + return mControllerNumber; + } + /** * Gets a generation number for this input device. * The generation number is incremented whenever the device is reconfigured and its @@ -422,6 +453,33 @@ public final class InputDevice implements Parcelable { return mGeneration; } + /** + * Gets the vendor id for the given device, if available. + *

    + * A vendor id uniquely identifies the company who manufactured the device. A value of 0 will + * be assigned where a vendor id is not available. + *

    + * + * @return The vendor id of a given device + */ + public int getVendorId() { + return mVendorId; + } + + /** + * Gets the product id for the given device, if available. + *

    + * A product id uniquely identifies which product within the address space of a given vendor, + * identified by the device's vendor id. A value of 0 will be assigned where a product id is + * not available. + *

    + * + * @return The product id of a given device + */ + public int getProductId() { + return mProductId; + } + /** * Gets the input device descriptor, which is a stable identifier for an input device. *

    @@ -520,6 +578,16 @@ public final class InputDevice implements Parcelable { return mKeyCharacterMap; } + /** + * Gets whether the device is capable of producing the list of keycodes. + * @param keys The list of android keycodes to check for. + * @return An array of booleans where each member specifies whether the device is capable of + * generating the keycode given by the corresponding value at the same index in the keys array. + */ + public boolean[] hasKeys(int... keys) { + return InputManager.getInstance().deviceHasKeys(mId, keys); + } + /** * Gets information about the range of values for a particular {@link MotionEvent} axis. * If the device supports multiple sources, the same axis may have different meanings @@ -611,6 +679,15 @@ public final class InputDevice implements Parcelable { } } + /** + * Reports whether the device has a button under its touchpad + * @return Whether the device has a button under its touchpad + * @hide + */ + public boolean hasButtonUnderPad() { + return mHasButtonUnderPad; + } + /** * Provides information about the range of values for a particular {@link MotionEvent} axis. * @@ -726,13 +803,17 @@ public final class InputDevice implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(mId); out.writeInt(mGeneration); + out.writeInt(mControllerNumber); out.writeString(mName); + out.writeInt(mVendorId); + out.writeInt(mProductId); out.writeString(mDescriptor); out.writeInt(mIsExternal ? 1 : 0); out.writeInt(mSources); out.writeInt(mKeyboardType); mKeyCharacterMap.writeToParcel(out, flags); out.writeInt(mHasVibrator ? 1 : 0); + out.writeInt(mHasButtonUnderPad ? 1 : 0); final int numRanges = mMotionRanges.size(); for (int i = 0; i < numRanges; i++) { diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 07a937c6f474e6ebf4874aa4fc42d80426488b83..1ecdf3071281ce207f018c9a8c5c0b386a2c3d56 100644 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -70,6 +70,7 @@ public abstract class InputEvent implements Parcelable { * Gets the source of the event. * * @return The event source or {@link InputDevice#SOURCE_UNKNOWN} if unknown. + * @see InputDevice#getSources */ public abstract int getSource(); diff --git a/core/java/android/view/InputFilter.java b/core/java/android/view/InputFilter.java index c25b87ba9d4822472f2164659c8539cf62d2318b..4aba30ca483e1f477b78ef1f457ba21e6e378e67 100644 --- a/core/java/android/view/InputFilter.java +++ b/core/java/android/view/InputFilter.java @@ -40,7 +40,7 @@ import android.view.WindowManagerPolicy; *

  • Input events are then asynchronously delivered to the input filter's * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to * applications as usual. The input filter only receives input events that were - * generated by input device; the input filter will not receive input events that were + * generated by an input device; the input filter will not receive input events that were * injected into the system by other means, such as by instrumentation.
  • *
  • The input filter processes and optionally transforms the stream of events. For example, * it may transform a sequence of motion events representing an accessibility gesture into @@ -68,7 +68,7 @@ import android.view.WindowManagerPolicy; * The input filter must take into account the fact that the input events coming from different * devices or even different sources all consist of distinct streams of input. * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify - * the source of the event and its semantics. There are be multiple sources of keys, + * the source of the event and its semantics. There may be multiple sources of keys, * touches and other input: they must be kept separate. *

    *

    Policy flags

    @@ -88,7 +88,7 @@ import android.view.WindowManagerPolicy; * The input filter should clear its internal state about the gesture and then send key or * motion events to the dispatcher to cancel any keys or pointers that are down. *

    - * Corollary: Events that set sent to the dispatcher should usually include the + * Corollary: Events that get sent to the dispatcher should usually include the * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! *

    * It may be prudent to disable automatic key repeating for synthetic key events diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 5db3909b0d0b2a198ed67e7475bb7f4a4170ae79..5a5fc1021d2ceb3a329a08b19aeb749e3b8dec09 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -629,8 +629,11 @@ public class KeyEvent extends InputEvent implements Parcelable { /** Key code constant: Brightness Up key. * Adjusts the screen brightness up. */ public static final int KEYCODE_BRIGHTNESS_UP = 221; + /** Key code constant: Audio Track key + * Switches the audio tracks. */ + public static final int KEYCODE_MEDIA_AUDIO_TRACK = 222; - private static final int LAST_KEYCODE = KEYCODE_BRIGHTNESS_UP; + private static final int LAST_KEYCODE = KEYCODE_MEDIA_AUDIO_TRACK; // NOTE: If you add a new keycode here you must also add it to: // isSystem() @@ -874,6 +877,7 @@ public class KeyEvent extends InputEvent implements Parcelable { names.append(KEYCODE_ASSIST, "KEYCODE_ASSIST"); names.append(KEYCODE_BRIGHTNESS_DOWN, "KEYCODE_BRIGHTNESS_DOWN"); names.append(KEYCODE_BRIGHTNESS_UP, "KEYCODE_BRIGHTNESS_UP"); + names.append(KEYCODE_MEDIA_AUDIO_TRACK, "KEYCODE_MEDIA_AUDIO_TRACK"); }; // Symbolic names of all metakeys in bit order from least significant to most significant. @@ -1833,6 +1837,19 @@ public class KeyEvent extends InputEvent implements Parcelable { } } + /** Whether key will, by default, trigger a click on the focused view. + * @hide + */ + public static final boolean isConfirmKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + return true; + default: + return false; + } + } + /** {@inheritDoc} */ @Override public final int getDeviceId() { diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index ee36097696153a8e2262cb0176074f850a3ad39b..db577f3627cf8d8012412927c10744dfb4b72813 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3003,13 +3003,13 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns a string that represents the symbolic name of the specified action + * Returns a string that represents the symbolic name of the specified unmasked action * such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant * such as "35" if unknown. * - * @param action The action. + * @param action The unmasked action. * @return The symbolic name of the specified action. - * @hide + * @see #getAction() */ public static String actionToString(int action) { switch (action) { @@ -3047,7 +3047,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * Returns a string that represents the symbolic name of the specified axis * such as "AXIS_X" or an equivalent numeric constant such as "42" if unknown. * - * @param axis The axis + * @param axis The axis. * @return The symbolic name of the specified axis. */ public static String axisToString(int axis) { @@ -3061,7 +3061,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * * @param symbolicName The symbolic name of the axis. * @return The axis or -1 if not found. - * @see KeyEvent#keycodeToString(int) + * @see KeyEvent#keyCodeToString(int) */ public static int axisFromString(String symbolicName) { if (symbolicName == null) { diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 51c5c7b52b6b2d1dcd998efbb46857c57442e90c..f36c78fd480783f111a1051a8b4f3fbe9dd13b35 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 mQuickScaleEnabled; + private float mCurrSpan; private float mPrevSpan; private float mInitialSpan; @@ -148,9 +152,15 @@ 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; + private static final float SCALE_FACTOR = .5f; + /** * Consistency verifier for debugging purposes. @@ -158,8 +168,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 +206,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 +306,14 @@ public class ScaleGestureDetector { final int action = event.getActionMasked(); + // Forward the event to check for double tap gesture + if (mQuickScaleEnabled) { + 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 +322,7 @@ public class ScaleGestureDetector { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = 0; + mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (streamComplete) { @@ -284,21 +334,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 +386,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,17 +399,20 @@ 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; mPrevSpanY = mCurrSpanY = spanY; mInitialSpan = mPrevSpan = mCurrSpan = span; } - if (!mInProgress && span >= mMinSpan && + + final int minSpan = inDoubleTapMode() ? mSpanSlop : mMinSpan; + if (!mInProgress && span >= minSpan && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; @@ -354,6 +428,7 @@ public class ScaleGestureDetector { mCurrSpan = span; boolean updatePrev = true; + if (mInProgress) { updatePrev = mListener.onScale(this); } @@ -369,6 +444,42 @@ 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) { + mQuickScaleEnabled = scales; + if (mQuickScaleEnabled && 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); + } + } + + /** + * Return whether the quick scale gesture, in which the user performs a double tap followed by a + * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}. + */ + public boolean isQuickScaleEnabled() { + return mQuickScaleEnabled; + } + /** * Returns {@code true} if a scale gesture is in progress. */ @@ -472,6 +583,16 @@ public class ScaleGestureDetector { * @return The current scaling factor. */ public float getScaleFactor() { + if (inDoubleTapMode()) { + // 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 + final boolean scaleUp = + (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) || + (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan)); + final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR); + return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff); + } return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; } @@ -493,4 +614,4 @@ public class ScaleGestureDetector { public long getEventTime() { return mCurrTime; } -} +} \ No newline at end of file diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index ae4005bfeea76d2a49b888c256fcd9ad1a72feff..1bfda2d9a45a76bcfbf4a334dd906f744540f4a3 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -36,7 +36,7 @@ public class Surface implements Parcelable { throws OutOfResourcesException; private static native int nativeCreateFromSurfaceControl(int surfaceControlNativeObject); - private static native void nativeLockCanvas(int nativeObject, Canvas canvas, Rect dirty) + private static native int nativeLockCanvas(int nativeObject, Canvas canvas, Rect dirty) throws OutOfResourcesException; private static native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas); @@ -71,8 +71,9 @@ public class Surface implements Parcelable { // Guarded state. final Object mLock = new Object(); // protects the native state private String mName; - int mNativeSurface; // package scope only for SurfaceControl access - private int mGenerationId; // incremented each time mNativeSurface changes + int mNativeObject; // package scope only for SurfaceControl access + private int mLockedObject; + private int mGenerationId; // incremented each time mNativeObject changes private final Canvas mCanvas = new CompatibleCanvas(); // A matrix to scale the matrix set by application. This is set to null for @@ -115,6 +116,7 @@ public class Surface implements Parcelable { * * @param surfaceTexture The {@link SurfaceTexture} that is updated by this * Surface. + * @throws OutOfResourcesException if the surface could not be created. */ public Surface(SurfaceTexture surfaceTexture) { if (surfaceTexture == null) { @@ -123,12 +125,7 @@ public class Surface implements Parcelable { synchronized (mLock) { mName = surfaceTexture.toString(); - try { - setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture)); - } catch (OutOfResourcesException ex) { - // We can't throw OutOfResourcesException because it would be an API change. - throw new RuntimeException(ex); - } + setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture)); } } @@ -158,8 +155,8 @@ public class Surface implements Parcelable { */ public void release() { synchronized (mLock) { - if (mNativeSurface != 0) { - nativeRelease(mNativeSurface); + if (mNativeObject != 0) { + nativeRelease(mNativeObject); setNativeObjectLocked(0); } } @@ -183,8 +180,8 @@ public class Surface implements Parcelable { */ public boolean isValid() { synchronized (mLock) { - if (mNativeSurface == 0) return false; - return nativeIsValid(mNativeSurface); + if (mNativeObject == 0) return false; + return nativeIsValid(mNativeObject); } } @@ -210,7 +207,7 @@ public class Surface implements Parcelable { public boolean isConsumerRunningBehind() { synchronized (mLock) { checkNotReleasedLocked(); - return nativeIsConsumerRunningBehind(mNativeSurface); + return nativeIsConsumerRunningBehind(mNativeObject); } } @@ -228,12 +225,22 @@ public class Surface implements Parcelable { * The caller may also pass null instead, in the case where the * entire surface should be redrawn. * @return A canvas for drawing into the surface. + * + * @throws IllegalArgumentException If the inOutDirty rectangle is not valid. + * @throws OutOfResourcesException If the canvas cannot be locked. */ public Canvas lockCanvas(Rect inOutDirty) - throws OutOfResourcesException, IllegalArgumentException { + throws Surface.OutOfResourcesException, IllegalArgumentException { synchronized (mLock) { checkNotReleasedLocked(); - nativeLockCanvas(mNativeSurface, mCanvas, inOutDirty); + if (mLockedObject != 0) { + // Ideally, nativeLockCanvas() would throw in this situation and prevent the + // double-lock, but that won't happen if mNativeObject was updated. We can't + // abandon the old mLockedObject because it might still be in use, so instead + // we just refuse to re-lock the Surface. + throw new IllegalStateException("Surface was already locked"); + } + mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty); return mCanvas; } } @@ -252,11 +259,21 @@ public class Surface implements Parcelable { synchronized (mLock) { checkNotReleasedLocked(); - nativeUnlockCanvasAndPost(mNativeSurface, canvas); + if (mNativeObject != mLockedObject) { + Log.w(TAG, "WARNING: Surface's mNativeObject (0x" + + Integer.toHexString(mNativeObject) + ") != mLockedObject (0x" + + Integer.toHexString(mLockedObject) +")"); + } + if (mLockedObject == 0) { + throw new IllegalStateException("Surface was not locked"); + } + nativeUnlockCanvasAndPost(mLockedObject, canvas); + nativeRelease(mLockedObject); + mLockedObject = 0; } } - /** + /** * @deprecated This API has been removed and is not supported. Do not use. */ @Deprecated @@ -298,8 +315,8 @@ public class Surface implements Parcelable { int newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr); synchronized (mLock) { - if (mNativeSurface != 0) { - nativeRelease(mNativeSurface); + if (mNativeObject != 0) { + nativeRelease(mNativeObject); } setNativeObjectLocked(newNativeObject); } @@ -319,13 +336,13 @@ public class Surface implements Parcelable { if (other != this) { final int newPtr; synchronized (other.mLock) { - newPtr = other.mNativeSurface; + newPtr = other.mNativeObject; other.setNativeObjectLocked(0); } synchronized (mLock) { - if (mNativeSurface != 0) { - nativeRelease(mNativeSurface); + if (mNativeObject != 0) { + nativeRelease(mNativeObject); } setNativeObjectLocked(newPtr); } @@ -343,8 +360,12 @@ public class Surface implements Parcelable { } synchronized (mLock) { + // nativeReadFromParcel() will either return mNativeObject, or + // create a new native Surface and return it after reducing + // the reference count on mNativeObject. Either way, it is + // not necessary to call nativeRelease() here. mName = source.readString(); - setNativeObjectLocked(nativeReadFromParcel(mNativeSurface, source)); + setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source)); } } @@ -355,7 +376,7 @@ public class Surface implements Parcelable { } synchronized (mLock) { dest.writeString(mName); - nativeWriteToParcel(mNativeSurface, dest); + nativeWriteToParcel(mNativeObject, dest); } if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { release(); @@ -365,32 +386,35 @@ public class Surface implements Parcelable { @Override public String toString() { synchronized (mLock) { - return "Surface(name=" + mName + ")"; + return "Surface(name=" + mName + ")/@0x" + + Integer.toHexString(System.identityHashCode(this)); } } private void setNativeObjectLocked(int ptr) { - if (mNativeSurface != ptr) { - if (mNativeSurface == 0 && ptr != 0) { + if (mNativeObject != ptr) { + if (mNativeObject == 0 && ptr != 0) { mCloseGuard.open("release"); - } else if (mNativeSurface != 0 && ptr == 0) { + } else if (mNativeObject != 0 && ptr == 0) { mCloseGuard.close(); } - mNativeSurface = ptr; + mNativeObject = ptr; mGenerationId += 1; } } private void checkNotReleasedLocked() { - if (mNativeSurface == 0) { + if (mNativeObject == 0) { throw new IllegalStateException("Surface has already been released."); } } /** - * Exception thrown when a surface couldn't be created or resized. + * Exception thrown when a Canvas couldn't be locked with {@link Surface#lockCanvas}, or + * when a SurfaceTexture could not successfully be allocated. */ - public static class OutOfResourcesException extends Exception { + @SuppressWarnings("serial") + public static class OutOfResourcesException extends RuntimeException { public OutOfResourcesException() { } public OutOfResourcesException(String name) { @@ -463,7 +487,7 @@ public class Surface implements Parcelable { public void getMatrix(Matrix m) { super.getMatrix(m); if (mOrigMatrix == null) { - mOrigMatrix = new Matrix(); + mOrigMatrix = new Matrix(); } mOrigMatrix.set(m); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index c6da84fffd39d5587189272bfd7f0be474faa571..b22d5cf32af5307737b73cadd86ef8ca4d0d333e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -24,6 +24,7 @@ import android.view.Surface; import android.os.IBinder; import android.os.SystemProperties; import android.util.Log; +import android.view.Surface.OutOfResourcesException; /** * SurfaceControl @@ -59,13 +60,14 @@ public class SurfaceControl { private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId); private static native IBinder nativeCreateDisplay(String name, boolean secure); + private static native void nativeDestroyDisplay(IBinder displayToken); private static native void nativeSetDisplaySurface( IBinder displayToken, int nativeSurfaceObject); private static native void nativeSetDisplayLayerStack( IBinder displayToken, int layerStack); private static native void nativeSetDisplayProjection( IBinder displayToken, int orientation, - int l, int t, int r, int b, + int l, int t, int r, int b, int L, int T, int R, int B); private static native boolean nativeGetDisplayInfo( IBinder displayToken, SurfaceControl.PhysicalDisplayInfo outInfo); @@ -74,23 +76,12 @@ public class SurfaceControl { private final CloseGuard mCloseGuard = CloseGuard.get(); - private String mName; + private final String mName; int mNativeObject; // package visibility only for Surface.java access private static final boolean HEADLESS = "1".equals( SystemProperties.get("ro.config.headless", "0")); - /** - * Exception thrown when a surface couldn't be created or resized. - */ - public static class OutOfResourcesException extends Exception { - public OutOfResourcesException() { - } - public OutOfResourcesException(String name) { - super(name); - } - } - /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */ /** @@ -103,7 +94,7 @@ public class SurfaceControl { * measures will be taken to disallow the surface's content to be copied * from another process. In particular, screenshots and VNC servers will * be disabled, but other measures can take place, for instance the - * surface might not be hardware accelerated. + * surface might not be hardware accelerated. * */ public static final int SECURE = 0x00000080; @@ -219,6 +210,8 @@ public class SurfaceControl { * @param h The surface initial height. * @param flags The surface creation flags. Should always include {@link #HIDDEN} * in the creation flags. + * + * @throws throws OutOfResourcesException If the SurfaceControl cannot be created. */ public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags) @@ -247,10 +240,10 @@ public class SurfaceControl { throw new OutOfResourcesException( "Couldn't allocate SurfaceControl native object"); } - + mCloseGuard.open("release"); } - + @Override protected void finalize() throws Throwable { try { @@ -300,7 +293,7 @@ public class SurfaceControl { if (mNativeObject == 0) throw new NullPointerException( "mNativeObject is null. Have you called release() already?"); } - + /* * set surface parameters. * needs to be inside open/closeTransaction block @@ -369,7 +362,7 @@ public class SurfaceControl { public void setWindowCrop(Rect crop) { checkNotReleased(); if (crop != null) { - nativeSetWindowCrop(mNativeObject, + nativeSetWindowCrop(mNativeObject, crop.left, crop.top, crop.right, crop.bottom); } else { nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0); @@ -397,19 +390,19 @@ public class SurfaceControl { public float xDpi; public float yDpi; public boolean secure; - + public PhysicalDisplayInfo() { } - + public PhysicalDisplayInfo(PhysicalDisplayInfo other) { copyFrom(other); } - + @Override public boolean equals(Object o) { return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o); } - + public boolean equals(PhysicalDisplayInfo other) { return other != null && width == other.width @@ -420,12 +413,12 @@ public class SurfaceControl { && yDpi == other.yDpi && secure == other.secure; } - + @Override public int hashCode() { return 0; // don't care } - + public void copyFrom(PhysicalDisplayInfo other) { width = other.width; height = other.height; @@ -435,7 +428,7 @@ public class SurfaceControl { yDpi = other.yDpi; secure = other.secure; } - + // For debugging purposes @Override public String toString() { @@ -481,7 +474,7 @@ public class SurfaceControl { throw new IllegalArgumentException("displayRect must not be null"); } nativeSetDisplayProjection(displayToken, orientation, - layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom, + layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom, displayRect.left, displayRect.top, displayRect.right, displayRect.bottom); } @@ -499,7 +492,7 @@ public class SurfaceControl { if (surface != null) { synchronized (surface.mLock) { - nativeSetDisplaySurface(displayToken, surface.mNativeSurface); + nativeSetDisplaySurface(displayToken, surface.mNativeObject); } } else { nativeSetDisplaySurface(displayToken, 0); @@ -513,6 +506,13 @@ public class SurfaceControl { return nativeCreateDisplay(name, secure); } + public static void destroyDisplay(IBinder displayToken) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + nativeDestroyDisplay(displayToken); + } + public static IBinder getBuiltInDisplay(int builtInDisplayId) { return nativeGetBuiltInDisplay(builtInDisplayId); } @@ -608,7 +608,7 @@ public class SurfaceControl { SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); return nativeScreenshot(displayToken, width, height, 0, 0, true); } - + private static void screenshot(IBinder display, Surface consumer, int width, int height, int minLayer, int maxLayer, boolean allLayers) { if (display == null) { diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java index 015a78e0daa6c7f66a4d5623d93a647f5078d54c..99fa2a4b138e1c6e96f3ab9dd1440a7d4991a45f 100644 --- a/core/java/android/view/SurfaceHolder.java +++ b/core/java/android/view/SurfaceHolder.java @@ -277,9 +277,6 @@ public interface SurfaceHolder { * *

    This method is intended to be used by frameworks which often need * direct access to the Surface object (usually to pass it to native code). - * When designing APIs always use SurfaceHolder to pass surfaces around - * as opposed to the Surface object itself. A rule of thumb is that - * application code should never have to call this method. * * @return Surface The surface. */ diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 793fb5e2ad45005bbd8e7080c92c2225c65d65c1..22d4c9bf7ab009208f5b90c1f5c7d53f88a98d89 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -43,7 +43,7 @@ import java.util.concurrent.locks.ReentrantLock; * You can control the format of this surface and, if you like, its size; the * SurfaceView takes care of placing the surface at the correct location on the * screen - * + * *

    The surface is Z ordered so that it is behind the window holding its * SurfaceView; the SurfaceView punches a hole in its window to allow its * surface to be displayed. The view hierarchy will take care of correctly @@ -52,7 +52,7 @@ import java.util.concurrent.locks.ReentrantLock; * buttons on top of the Surface, though note however that it can have an * impact on performance since a full alpha-blended composite will be performed * each time the Surface changes. - * + * *

    The transparent region that makes the surface visible is based on the * layout positions in the view hierarchy. If the post-layout transform * properties are used to draw a sibling view on top of the SurfaceView, the @@ -60,16 +60,16 @@ import java.util.concurrent.locks.ReentrantLock; * *

    Access to the underlying surface is provided via the SurfaceHolder interface, * which can be retrieved by calling {@link #getHolder}. - * + * *

    The Surface will be created for you while the SurfaceView's window is * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the * Surface is created and destroyed as the window is shown and hidden. - * + * *

    One of the purposes of this class is to provide a surface in which a * secondary thread can render into the screen. If you are going to use it * this way, you need to be aware of some threading semantics: - * + * *

      *
    • All SurfaceView and * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called @@ -91,7 +91,7 @@ public class SurfaceView extends View { = new ArrayList(); final int[] mLocation = new int[2]; - + final ReentrantLock mSurfaceLock = new ReentrantLock(); final Surface mSurface = new Surface(); // Current surface in use final Surface mNewSurface = new Surface(); // New surface we are switching to @@ -106,13 +106,13 @@ public class SurfaceView extends View { final Rect mOverscanInsets = new Rect(); final Rect mContentInsets = new Rect(); final Configuration mConfiguration = new Configuration(); - + static final int KEEP_SCREEN_ON_MSG = 1; static final int GET_NEW_SURFACE_MSG = 2; static final int UPDATE_WINDOW_MSG = 3; - + int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; - + boolean mIsCreating = false; final Handler mHandler = new Handler() { @@ -131,14 +131,15 @@ public class SurfaceView extends View { } } }; - + final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() { + @Override public void onScrollChanged() { updateWindow(false, false); } }; - + boolean mRequestedVisible = false; boolean mWindowVisibility = false; boolean mViewVisibility = false; @@ -152,7 +153,7 @@ public class SurfaceView extends View { boolean mHaveFrame = false; boolean mSurfaceCreated = false; long mLastLockTime = 0; - + boolean mVisible = false; int mLeft = -1; int mTop = -1; @@ -160,7 +161,6 @@ public class SurfaceView extends View { int mHeight = -1; int mFormat = -1; final Rect mSurfaceFrame = new Rect(); - Rect mTmpDirty; int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; boolean mUpdateWindowNeeded; boolean mReportDrawNeeded; @@ -170,7 +170,7 @@ public class SurfaceView extends View { new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - // reposition ourselves where the surface is + // reposition ourselves where the surface is mHaveFrame = getWidth() > 0 && getHeight() > 0; updateWindow(false, false); return true; @@ -182,7 +182,7 @@ public class SurfaceView extends View { super(context); init(); } - + public SurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); @@ -196,11 +196,11 @@ public class SurfaceView extends View { private void init() { setWillNotDraw(true); } - + /** * Return the SurfaceHolder providing access and control over this * SurfaceView's underlying surface. - * + * * @return SurfaceHolder The holder of the surface. */ public SurfaceHolder getHolder() { @@ -286,7 +286,7 @@ public class SurfaceView extends View { : getDefaultSize(0, heightMeasureSpec); setMeasuredDimension(width, height); } - + /** @hide */ @Override protected boolean setFrame(int left, int top, int right, int bottom) { @@ -300,7 +300,7 @@ public class SurfaceView extends View { if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { return super.gatherTransparentRegion(region); } - + boolean opaque = true; if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { // this view draws, remove it from the transparent region @@ -351,10 +351,10 @@ public class SurfaceView extends View { * regular surface view in the window (but still behind the window itself). * This is typically used to place overlays on top of an underlying media * surface view. - * + * *

      Note that this must be set before the surface view's containing * window is attached to the window manager. - * + * *

      Calling this overrides any previous call to {@link #setZOrderOnTop}. */ public void setZOrderMediaOverlay(boolean isMediaOverlay) { @@ -362,7 +362,7 @@ public class SurfaceView extends View { ? WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY : WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; } - + /** * Control whether the surface view's surface is placed on top of its * window. Normally it is placed behind the window, to allow it to @@ -370,10 +370,10 @@ public class SurfaceView extends View { * hierarchy. By setting this, you cause it to be placed above the * window. This means that none of the contents of the window this * SurfaceView is in will be visible on top of its surface. - * + * *

      Note that this must be set before the surface view's containing * window is attached to the window manager. - * + * *

      Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. */ public void setZOrderOnTop(boolean onTop) { @@ -428,7 +428,7 @@ public class SurfaceView extends View { if (mTranslator != null) { mSurface.setCompatibilityTranslator(mTranslator); } - + int myWidth = mRequestedWidth; if (myWidth <= 0) myWidth = getWidth(); int myHeight = mRequestedHeight; @@ -459,7 +459,7 @@ public class SurfaceView extends View { mFormat = mRequestedFormat; // Scaling/Translate window's layout here because mLayout is not used elsewhere. - + // Places the window relative mLayout.x = mLeft; mLayout.y = mTop; @@ -468,7 +468,7 @@ public class SurfaceView extends View { if (mTranslator != null) { mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout); } - + mLayout.format = mRequestedFormat; mLayout.flags |=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE @@ -478,7 +478,8 @@ public class SurfaceView extends View { | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE ; if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) { - mLayout.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + mLayout.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; } mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -490,7 +491,7 @@ public class SurfaceView extends View { mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout, mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets); } - + boolean realSizeChanged; boolean reportDrawNeeded; @@ -502,7 +503,7 @@ public class SurfaceView extends View { reportDrawNeeded = mReportDrawNeeded; mReportDrawNeeded = false; mDrawingStopped = !visible; - + if (DEBUG) Log.i(TAG, "Cur surface: " + mSurface); relayoutResult = mSession.relayout( @@ -528,7 +529,7 @@ public class SurfaceView extends View { mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f); mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f); } - + final int surfaceWidth = mSurfaceFrame.right; final int surfaceHeight = mSurfaceFrame.bottom; realSizeChanged = mLastSurfaceWidth != surfaceWidth @@ -668,10 +669,12 @@ public class SurfaceView extends View { } } + @Override public void dispatchAppVisibility(boolean visible) { // The point of SurfaceView is to let the app control the surface. } + @Override public void dispatchGetNewSurface() { SurfaceView surfaceView = mSurfaceView.get(); if (surfaceView != null) { @@ -680,10 +683,12 @@ public class SurfaceView extends View { } } + @Override public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled); } + @Override public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { } @@ -691,30 +696,34 @@ public class SurfaceView extends View { int mCurHeight = -1; } - private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { - + private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { + private static final String LOG_TAG = "SurfaceHolder"; - + + @Override public boolean isCreating() { return mIsCreating; } + @Override public void addCallback(Callback callback) { synchronized (mCallbacks) { - // This is a linear search, but in practice we'll + // This is a linear search, but in practice we'll // have only a couple callbacks, so it doesn't matter. - if (mCallbacks.contains(callback) == false) { + if (mCallbacks.contains(callback) == false) { mCallbacks.add(callback); } } } + @Override public void removeCallback(Callback callback) { synchronized (mCallbacks) { mCallbacks.remove(callback); } } - + + @Override public void setFixedSize(int width, int height) { if (mRequestedWidth != width || mRequestedHeight != height) { mRequestedWidth = width; @@ -723,6 +732,7 @@ public class SurfaceView extends View { } } + @Override public void setSizeFromLayout() { if (mRequestedWidth != -1 || mRequestedHeight != -1) { mRequestedWidth = mRequestedHeight = -1; @@ -730,6 +740,7 @@ public class SurfaceView extends View { } } + @Override public void setFormat(int format) { // for backward compatibility reason, OPAQUE always @@ -746,15 +757,17 @@ public class SurfaceView extends View { /** * @deprecated setType is now ignored. */ + @Override @Deprecated public void setType(int type) { } + @Override public void setKeepScreenOn(boolean screenOn) { Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG); msg.arg1 = screenOn ? 1 : 0; mHandler.sendMessage(msg); } - + /** * Gets a {@link Canvas} for drawing into the SurfaceView's Surface * @@ -764,6 +777,7 @@ public class SurfaceView extends View { * The caller must redraw the entire surface. * @return A canvas for drawing into the surface. */ + @Override public Canvas lockCanvas() { return internalLockCanvas(null); } @@ -783,6 +797,7 @@ public class SurfaceView extends View { * entire surface should be redrawn. * @return A canvas for drawing into the surface. */ + @Override public Canvas lockCanvas(Rect inOutDirty) { return internalLockCanvas(inOutDirty); } @@ -795,14 +810,6 @@ public class SurfaceView extends View { Canvas c = null; if (!mDrawingStopped && mWindow != null) { - if (dirty == null) { - if (mTmpDirty == null) { - mTmpDirty = new Rect(); - } - mTmpDirty.set(mSurfaceFrame); - dirty = mTmpDirty; - } - try { c = mSurface.lockCanvas(dirty); } catch (Exception e) { @@ -815,7 +822,7 @@ public class SurfaceView extends View { mLastLockTime = SystemClock.uptimeMillis(); return c; } - + // If the Surface is not ready to be drawn, then return null, // but throttle calls to this function so it isn't called more // than every 100ms. @@ -830,7 +837,7 @@ public class SurfaceView extends View { } mLastLockTime = now; mSurfaceLock.unlock(); - + return null; } @@ -840,15 +847,18 @@ public class SurfaceView extends View { * * @param canvas The canvas previously obtained from {@link #lockCanvas}. */ + @Override public void unlockCanvasAndPost(Canvas canvas) { mSurface.unlockCanvasAndPost(canvas); mSurfaceLock.unlock(); } + @Override public Surface getSurface() { return mSurface; } + @Override public Rect getSurfaceFrame() { return mSurfaceFrame; } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 244dc337d6f98dd6f711cf44fcf2b7666a388ed9..47f7628f8e88769cfe0fb3467210647133b0fb1a 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -213,8 +213,8 @@ public class TextureView extends View { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mLayer != null && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { - boolean success = mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() { + if (mLayer != null) { + boolean success = executeHardwareAction(new Runnable() { @Override public void run() { destroySurface(); @@ -322,7 +322,7 @@ public class TextureView extends View { protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mSurface != null) { - nSetDefaultBufferSize(mSurface, getWidth(), getHeight()); + mSurface.setDefaultBufferSize(getWidth(), getHeight()); updateLayer(); if (mListener != null) { mListener.onSurfaceTextureSizeChanged(mSurface, getWidth(), getHeight()); @@ -362,7 +362,7 @@ public class TextureView extends View { // Create a new SurfaceTexture for the layer. mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer); } - nSetDefaultBufferSize(mSurface, getWidth(), getHeight()); + mSurface.setDefaultBufferSize(getWidth(), getHeight()); nCreateNativeWindow(mSurface); mUpdateListener = new SurfaceTexture.OnFrameAvailableListener() { @@ -399,7 +399,7 @@ public class TextureView extends View { mMatrixChanged = true; mAttachInfo.mHardwareRenderer.setSurfaceTexture(mLayer, mSurface); - nSetDefaultBufferSize(mSurface, getWidth(), getHeight()); + mSurface.setDefaultBufferSize(getWidth(), getHeight()); } applyUpdate(); @@ -656,13 +656,19 @@ public class TextureView extends View { * rectangle. Every pixel within that rectangle must be written; however * pixels outside the dirty rectangle will be preserved by the next call * to lockCanvas(). + * + * This method can return null if the underlying surface texture is not + * available (see {@link #isAvailable()} or if the surface texture is + * already connected to an image producer (for instance: the camera, + * OpenGL, a media player, etc.) * * @param dirty Area of the surface that will be modified. * @return A Canvas used to draw into the surface. * * @see #lockCanvas() - * @see #unlockCanvasAndPost(android.graphics.Canvas) + * @see #unlockCanvasAndPost(android.graphics.Canvas) + * @see #isAvailable() */ public Canvas lockCanvas(Rect dirty) { if (!isAvailable()) return null; @@ -672,7 +678,9 @@ public class TextureView extends View { } synchronized (mNativeWindowLock) { - nLockCanvas(mNativeWindow, mCanvas, dirty); + if (!nLockCanvas(mNativeWindow, mCanvas, dirty)) { + return null; + } } mSaveCount = mCanvas.save(); @@ -808,9 +816,6 @@ public class TextureView extends View { private native void nCreateNativeWindow(SurfaceTexture surface); private native void nDestroyNativeWindow(); - private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture, - int width, int height); - - private static native void nLockCanvas(int nativeWindow, Canvas canvas, Rect dirty); + private static native boolean nLockCanvas(int nativeWindow, Canvas canvas, Rect dirty); private static native void nUnlockCanvasAndPost(int nativeWindow, Canvas canvas); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 50638aaf449173aec893256a6022f89a79ca6f0f..e2d98edafc49afde5477b711c655165deee24324 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -18,6 +18,7 @@ package android.view; import android.content.ClipData; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -40,7 +41,6 @@ import android.graphics.Shader; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManagerGlobal; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -52,10 +52,13 @@ import android.os.SystemProperties; import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; +import android.util.LayoutDirection; import android.util.Log; +import android.util.LongSparseLongArray; import android.util.Pools.SynchronizedPool; import android.util.Property; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; import android.view.AccessibilityIterators.TextSegmentIterator; @@ -689,8 +692,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int NO_ID = -1; + /** + * Signals that compatibility booleans have been initialized according to + * target SDK versions. + */ + private static boolean sCompatibilityDone = false; + + /** + * Use the old (broken) way of building MeasureSpecs. + */ private static boolean sUseBrokenMakeMeasureSpec = false; + /** + * Ignore any optimizations using the measure cache. + */ + private static boolean sIgnoreMeasureCache = false; + /** * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when * calling setFlags. @@ -1122,7 +1139,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see android.graphics.drawable.Drawable * @see #getDrawableState() - * @hide */ protected static final int[] PRESSED_STATE_SET; /** @@ -1563,6 +1579,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED; + SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent; + /** * The view's tag. * {@hide} @@ -1735,47 +1753,41 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Masks for mPrivateFlags2, as generated by dumpFlags(): * - * -------|-------|-------|-------| - * PFLAG2_TEXT_ALIGNMENT_FLAGS[0] - * PFLAG2_TEXT_DIRECTION_FLAGS[0] - * 1 PFLAG2_DRAG_CAN_ACCEPT - * 1 PFLAG2_DRAG_HOVERED - * 1 PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT - * 11 PFLAG2_TEXT_DIRECTION_MASK_SHIFT - * 1 1 PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT - * 11 PFLAG2_LAYOUT_DIRECTION_MASK - * 11 1 PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT - * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL - * 1 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT - * 1 1 PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT - * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED - * 11 PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK - * 1 PFLAG2_TEXT_DIRECTION_FLAGS[1] - * 1 PFLAG2_TEXT_DIRECTION_FLAGS[2] - * 11 PFLAG2_TEXT_DIRECTION_FLAGS[3] - * 1 PFLAG2_TEXT_DIRECTION_FLAGS[4] - * 1 1 PFLAG2_TEXT_DIRECTION_FLAGS[5] - * 111 PFLAG2_TEXT_DIRECTION_MASK - * 1 PFLAG2_TEXT_DIRECTION_RESOLVED - * 1 PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT - * 111 PFLAG2_TEXT_DIRECTION_RESOLVED_MASK - * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[1] - * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[2] - * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[3] - * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[4] - * 1 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[5] - * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[6] - * 111 PFLAG2_TEXT_ALIGNMENT_MASK - * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED - * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT - * 111 PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK - * 11 PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK - * 1 PFLAG2_HAS_TRANSIENT_STATE - * 1 PFLAG2_ACCESSIBILITY_FOCUSED - * 1 PFLAG2_ACCESSIBILITY_STATE_CHANGED - * 1 PFLAG2_VIEW_QUICK_REJECTED - * 1 PFLAG2_PADDING_RESOLVED - * -------|-------|-------|-------| + * |-------|-------|-------|-------| + * 1 PFLAG2_DRAG_CAN_ACCEPT + * 1 PFLAG2_DRAG_HOVERED + * 11 PFLAG2_LAYOUT_DIRECTION_MASK + * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL + * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED + * 11 PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK + * 1 PFLAG2_TEXT_DIRECTION_FLAGS[1] + * 1 PFLAG2_TEXT_DIRECTION_FLAGS[2] + * 11 PFLAG2_TEXT_DIRECTION_FLAGS[3] + * 1 PFLAG2_TEXT_DIRECTION_FLAGS[4] + * 1 1 PFLAG2_TEXT_DIRECTION_FLAGS[5] + * 111 PFLAG2_TEXT_DIRECTION_MASK + * 1 PFLAG2_TEXT_DIRECTION_RESOLVED + * 1 PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT + * 111 PFLAG2_TEXT_DIRECTION_RESOLVED_MASK + * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[1] + * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[2] + * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[3] + * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[4] + * 1 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[5] + * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[6] + * 111 PFLAG2_TEXT_ALIGNMENT_MASK + * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED + * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT + * 111 PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK + * 111 PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK + * 11 PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK + * 1 PFLAG2_ACCESSIBILITY_FOCUSED + * 1 PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED + * 1 PFLAG2_VIEW_QUICK_REJECTED + * 1 PFLAG2_PADDING_RESOLVED + * 1 PFLAG2_DRAWABLE_RESOLVED + * 1 PFLAG2_HAS_TRANSIENT_STATE + * |-------|-------|-------|-------| */ /** @@ -1797,25 +1809,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Horizontal layout direction of this view is from Left to Right. * Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_LTR = 0; + public static final int LAYOUT_DIRECTION_LTR = LayoutDirection.LTR; /** * Horizontal layout direction of this view is from Right to Left. * Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_RTL = 1; + public static final int LAYOUT_DIRECTION_RTL = LayoutDirection.RTL; /** * Horizontal layout direction of this view is inherited from its parent. * Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_INHERIT = 2; + public static final int LAYOUT_DIRECTION_INHERIT = LayoutDirection.INHERIT; /** * Horizontal layout direction of this view is from deduced from the default language * script for the locale. Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_LOCALE = 3; + public static final int LAYOUT_DIRECTION_LOCALE = LayoutDirection.LOCALE; /** * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) @@ -1872,15 +1884,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR; - /** - * Indicates that the view is tracking some sort of transient state - * that the app should not need to be aware of, but that the framework - * should take special care to preserve. - * - * @hide - */ - static final int PFLAG2_HAS_TRANSIENT_STATE = 0x1 << 22; - /** * Text direction is inherited thru {@link ViewGroup} */ @@ -2117,6 +2120,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002; + /** + * The view is not important for accessibility, nor are any of its + * descendant views. + */ + public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 0x00000004; + /** * The default whether the view is important for accessibility. */ @@ -2127,19 +2136,63 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * whether a view is important for accessibility. */ static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO - | IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO) + | IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO + | IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + /** + * Shift for the bits in {@link #mPrivateFlags2} related to the + * "accessibilityLiveRegion" attribute. + */ + static final int PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT = 23; + + /** + * Live region mode specifying that accessibility services should not + * automatically announce changes to this view. This is the default live + * region mode for most views. + *

      + * Use with {@link #setAccessibilityLiveRegion(int)}. + */ + public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0x00000000; + + /** + * Live region mode specifying that accessibility services should announce + * changes to this view. + *

      + * Use with {@link #setAccessibilityLiveRegion(int)}. + */ + public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001; + + /** + * Live region mode specifying that accessibility services should interrupt + * ongoing speech to immediately announce changes to this view. + *

      + * Use with {@link #setAccessibilityLiveRegion(int)}. + */ + public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 0x00000002; + + /** + * The default whether the view is important for accessibility. + */ + static final int ACCESSIBILITY_LIVE_REGION_DEFAULT = ACCESSIBILITY_LIVE_REGION_NONE; + + /** + * Mask for obtaining the bits which specify a view's accessibility live + * region mode. + */ + static final int PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK = (ACCESSIBILITY_LIVE_REGION_NONE + | ACCESSIBILITY_LIVE_REGION_POLITE | ACCESSIBILITY_LIVE_REGION_ASSERTIVE) + << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT; + /** * Flag indicating whether a view has accessibility focus. */ - static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x00000040 << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x04000000; /** - * Flag indicating whether a view state for accessibility has changed. + * Flag whether the accessibility state of the subtree rooted at this view changed. */ - static final int PFLAG2_ACCESSIBILITY_STATE_CHANGED = 0x00000080 - << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + static final int PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED = 0x08000000; /** * Flag indicating whether a view failed the quickReject() check in draw(). This condition @@ -2162,6 +2215,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG2_DRAWABLE_RESOLVED = 0x40000000; + /** + * Indicates that the view is tracking some sort of transient state + * that the app should not need to be aware of, but that the framework + * should take special care to preserve. + */ + static final int PFLAG2_HAS_TRANSIENT_STATE = 0x80000000; + /** * Group of bits indicating that RTL properties resolution is done. */ @@ -2191,6 +2251,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_VIEW_IS_ANIMATING_ALPHA = 0x2; + /** + * Flag indicating that the view has been through at least one layout since it + * was last attached to a window. + */ + static final int PFLAG3_IS_LAID_OUT = 0x4; + + /** + * Flag indicating that a call to measure() was skipped and should be done + * instead when layout() is invoked. + */ + static final int PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT = 0x8; + + /** + * Flag indicating that an overridden method correctly called down to + * the superclass implementation as required by the API spec. + */ + static final int PFLAG3_CALLED_SUPER = 0x10; + /* End of masks for mPrivateFlags3 */ @@ -2341,7 +2419,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * allows it to avoid artifacts when switching in and out of that mode, at * the expense that some of its user interface may be covered by screen * decorations when they are shown. You can perform layout of your inner - * UI elements to account for the navagation system UI through the + * UI elements to account for the navigation system UI through the * {@link #fitSystemWindows(Rect)} method. */ public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200; @@ -2358,6 +2436,34 @@ 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 remain interactive when + * hiding the navigation bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. If this flag is + * not set, {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} will be force cleared by the system on any + * user interaction. + *

      Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only + * has an effect when used in combination with that flag.

      + */ + public static final int SYSTEM_UI_FLAG_IMMERSIVE = 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. + *

      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. + *

      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.

      + */ + public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000; + /** * @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead. */ @@ -2477,6 +2583,66 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int STATUS_BAR_DISABLE_SEARCH = 0x02000000; + /** + * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked + * out of the public fields to keep the undefined bits out of the developer's way. + * + * Flag to specify that the status bar is displayed in transient mode. + */ + public static final int STATUS_BAR_TRANSIENT = 0x04000000; + + /** + * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked + * out of the public fields to keep the undefined bits out of the developer's way. + * + * Flag to specify that the navigation bar is displayed in transient mode. + */ + public static final int NAVIGATION_BAR_TRANSIENT = 0x08000000; + + /** + * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked + * out of the public fields to keep the undefined bits out of the developer's way. + * + * Flag to specify that the hidden status bar would like to be shown. + */ + public static final int STATUS_BAR_UNHIDE = 0x10000000; + + /** + * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked + * out of the public fields to keep the undefined bits out of the developer's way. + * + * Flag to specify that the hidden navigation bar would like to be shown. + */ + public static final int NAVIGATION_BAR_UNHIDE = 0x20000000; + + /** + * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked + * out of the public fields to keep the undefined bits out of the developer's way. + * + * Flag to specify that the status bar is displayed in translucent mode. + */ + public static final int STATUS_BAR_TRANSLUCENT = 0x40000000; + + /** + * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked + * out of the public fields to keep the undefined bits out of the developer's way. + * + * Flag to specify that the navigation bar is displayed in translucent mode. + */ + public static final int NAVIGATION_BAR_TRANSLUCENT = 0x80000000; + /** * @hide */ @@ -2754,6 +2920,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @ViewDebug.ExportedProperty float mAlpha = 1f; + + /** + * The opacity of the view as manipulated by the Fade transition. This is a hidden + * property only used by transitions, which is composited with the other alpha + * values to calculate the final visual alpha value. + */ + float mTransitionAlpha = 1f; } TransformationInfo mTransformationInfo; @@ -2938,6 +3111,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ int mOldHeightMeasureSpec = Integer.MIN_VALUE; + private LongSparseLongArray mMeasureCache; + @ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_") private Drawable mBackground; @@ -3011,18 +3186,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int[] mDrawableState = null; - /** - * Set to true when drawing cache is enabled and cannot be created. - * - * @hide - */ - public boolean mCachingFailed; - - private Bitmap mDrawingCache; - private Bitmap mUnscaledDrawingCache; - private HardwareLayer mHardwareLayer; - DisplayList mDisplayList; - /** * When this view has focus and the next focus is {@link #FOCUS_LEFT}, * the user may specify which view to go to next. @@ -3215,6 +3378,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mLayerType = LAYER_TYPE_NONE; Paint mLayerPaint; Rect mLocalDirtyRect; + private HardwareLayer mHardwareLayer; + + /** + * Set to true when drawing cache is enabled and cannot be created. + * + * @hide + */ + public boolean mCachingFailed; + private Bitmap mDrawingCache; + private Bitmap mUnscaledDrawingCache; + + DisplayList mDisplayList; /** * Set to true when the view is sending hover accessibility events because it @@ -3266,10 +3441,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; - if (!sUseBrokenMakeMeasureSpec && context.getApplicationInfo().targetSdkVersion <= - Build.VERSION_CODES.JELLY_BEAN_MR1 ) { + if (!sCompatibilityDone && context != null) { + final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; + // Older apps may need this compatibility hack for measurement. - sUseBrokenMakeMeasureSpec = true; + sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1; + + // Older apps expect onMeasure() to always be called on a layout pass, regardless + // of whether a layout was requested on that View. + sIgnoreMeasureCache = targetSdkVersion < KITKAT; + + sCompatibilityDone = true; } } @@ -3305,17 +3487,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; @@ -3666,6 +3847,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setImportantForAccessibility(a.getInt(attr, IMPORTANT_FOR_ACCESSIBILITY_DEFAULT)); break; + case R.styleable.View_accessibilityLiveRegion: + setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT)); + break; } } @@ -4418,10 +4602,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusChanged(true, direction, previouslyFocusedRect); refreshDrawableState(); - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } } } @@ -4511,10 +4691,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, System.out.println(this + " clearFocus()"); } + clearFocusInternal(true, true); + } + + /** + * Clears focus from the view, optionally propagating the change up through + * the parent hierarchy and requesting that the root view place new focus. + * + * @param propagate whether to propagate the change up through the parent + * hierarchy + * @param refocus when propagate is true, specifies whether to request the + * root view place new focus + */ + void clearFocusInternal(boolean propagate, boolean refocus) { if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { mPrivateFlags &= ~PFLAG_FOCUSED; - if (mParent != null) { + if (propagate && mParent != null) { mParent.clearChildFocus(this); } @@ -4522,13 +4715,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, refreshDrawableState(); - if (!rootViewRequestFocus()) { + if (propagate && (!refocus || !rootViewRequestFocus())) { notifyGlobalFocusCleared(this); } - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } } } @@ -4539,32 +4728,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } boolean rootViewRequestFocus() { - View root = getRootView(); - if (root != null) { - return root.requestFocus(); - } - return false; + final View root = getRootView(); + return root != null && root.requestFocus(); } /** * Called internally by the view system when a new view is getting focus. * This is what clears the old focus. + *

      + * NOTE: The parent view's focused child must be updated manually + * after calling this method. Otherwise, the view hierarchy may be left in + * an inconstent state. */ void unFocus() { if (DBG) { System.out.println(this + " unFocus()"); } - if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { - mPrivateFlags &= ~PFLAG_FOCUSED; - - onFocusChanged(false, 0, null); - refreshDrawableState(); - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } - } + clearFocusInternal(false, false); } /** @@ -4614,9 +4795,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { if (gainFocus) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - } + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } else { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } InputMethodManager imm = InputMethodManager.peekInstance(); @@ -4673,6 +4855,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see AccessibilityDelegate */ public void sendAccessibilityEvent(int eventType) { + // Excluded views do not send accessibility events. + if (!includeForAccessibility()) { + return; + } if (mAccessibilityDelegate != null) { mAccessibilityDelegate.sendAccessibilityEvent(this, eventType); } else { @@ -4840,7 +5026,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Note: Called from the default {@link AccessibilityDelegate}. */ void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { - } /** @@ -5097,6 +5282,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setAccessibilityFocused(isAccessibilityFocused()); info.setSelected(isSelected()); info.setLongClickable(isLongClickable()); + info.setLiveRegion(getAccessibilityLiveRegion()); // TODO: These make sense only if we are in an AdapterView but all // views can be selected. Maybe from accessibility perspective @@ -5190,7 +5376,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, View view = (View) current; // We have attach info so this view is attached and there is no // need to check whether we reach to ViewRootImpl on the way up. - if (view.getAlpha() <= 0 || view.getVisibility() != VISIBLE) { + if (view.getAlpha() <= 0 || view.getTransitionAlpha() <= 0 || + view.getVisibility() != VISIBLE) { return false; } current = view.mParent; @@ -5331,9 +5518,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mContentDescription = contentDescription; final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0; if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + notifySubtreeAccessibilityStateChangedIfNeeded(); + } else { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); } - notifyAccessibilityStateChanged(); } /** @@ -5657,7 +5847,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * which you would like to ensure are not being covered. * *

      The default implementation of this method simply applies the content - * inset's to the view's padding, consuming that content (modifying the + * insets to the view's padding, consuming that content (modifying the * insets to be 0), and returning true. This behavior is off by default, but can * be enabled through {@link #setFitsSystemWindows(boolean)}. * @@ -5694,8 +5884,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} you must not modify * the insets or else you and Android will be unhappy. * - * @return Return true if this view applied the insets and it should not - * continue propagating further down the hierarchy, false otherwise. + * @return {@code true} if this view applied the insets and it should not + * continue propagating further down the hierarchy, {@code false} otherwise. * @see #getFitsSystemWindows() * @see #setFitsSystemWindows(boolean) * @see #setSystemUiVisibility(int) @@ -5766,15 +5956,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Check for state of {@link #setFitsSystemWindows(boolean). If this method - * returns true, the default implementation of {@link #fitSystemWindows(Rect)} + * Check for state of {@link #setFitsSystemWindows(boolean)}. If this method + * returns {@code true}, the default implementation of {@link #fitSystemWindows(Rect)} * will be executed. * - * @return Returns true if the default implementation of + * @return {@code true} if the default implementation of * {@link #fitSystemWindows(Rect)} will be executed. * * @attr ref android.R.styleable#View_fitsSystemWindows - * @see #setFitsSystemWindows() + * @see #setFitsSystemWindows(boolean) * @see #fitSystemWindows(Rect) * @see #setSystemUiVisibility(int) */ @@ -5863,6 +6053,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Invalidate too, since the default behavior for views is to be // be drawn at 50% alpha rather than to change the drawable. invalidate(true); + + if (!enabled) { + cancelPendingInputEvents(); + } } /** @@ -6109,6 +6303,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Returns true if this view is currently attached to a window. + */ + public boolean isAttachedToWindow() { + return mAttachInfo != null; + } + + /** + * Returns true if this view has been through at least one layout since it + * was last attached to or detached from a window. + */ + public boolean isLaidOut() { + return (mPrivateFlags3 & PFLAG3_IS_LAID_OUT) == PFLAG3_IS_LAID_OUT; + } + /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on @@ -6578,8 +6787,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; } @@ -6613,7 +6823,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); - notifyAccessibilityStateChanged(); return true; } return false; @@ -6676,7 +6885,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED; invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); - notifyAccessibilityStateChanged(); } } @@ -6824,18 +7032,73 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #IMPORTANT_FOR_ACCESSIBILITY_YES * @see #IMPORTANT_FOR_ACCESSIBILITY_NO + * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO */ @ViewDebug.ExportedProperty(category = "accessibility", mapping = { @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_AUTO, to = "auto"), @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_YES, to = "yes"), - @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO, to = "no") + @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO, to = "no"), + @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, + to = "noHideDescendants") }) public int getImportantForAccessibility() { return (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK) >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; } + /** + * Sets the live region mode for this view. This indicates to accessibility + * services whether they should automatically notify the user about changes + * to the view's content description or text, or to the content descriptions + * or text of the view's children (where applicable). + *

      + * For example, in a login screen with a TextView that displays an "incorrect + * password" notification, that view should be marked as a live region with + * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. + *

      + * To disable change notifications for this view, use + * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region + * mode for most views. + *

      + * To indicate that the user should be notified of changes, use + * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. + *

      + * If the view's changes should interrupt ongoing speech and notify the user + * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. + * + * @param mode The live region mode for this view, one of: + *

        + *
      • {@link #ACCESSIBILITY_LIVE_REGION_NONE} + *
      • {@link #ACCESSIBILITY_LIVE_REGION_POLITE} + *
      • {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE} + *
      + * @attr ref android.R.styleable#View_accessibilityLiveRegion + */ + public void setAccessibilityLiveRegion(int mode) { + if (mode != getAccessibilityLiveRegion()) { + mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; + mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT) + & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + } + + /** + * Gets the live region mode for this View. + * + * @return The live region mode for the view. + * + * @attr ref android.R.styleable#View_accessibilityLiveRegion + * + * @see #setAccessibilityLiveRegion(int) + */ + public int getAccessibilityLiveRegion() { + return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK) + >> PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT; + } + /** * Sets how to determine whether this view is important for accessibility * which is if it fires accessibility events and if it is reported to @@ -6847,14 +7110,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #IMPORTANT_FOR_ACCESSIBILITY_YES * @see #IMPORTANT_FOR_ACCESSIBILITY_NO + * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO */ public void setImportantForAccessibility(int mode) { - if (mode != getImportantForAccessibility()) { + final int oldMode = getImportantForAccessibility(); + if (mode != oldMode) { + // If we're moving between AUTO and another state, we might not need + // to send a subtree changed notification. We'll store the computed + // importance, since we'll need to check it later to make sure. + final boolean maySkipNotify = oldMode == IMPORTANT_FOR_ACCESSIBILITY_AUTO + || mode == IMPORTANT_FOR_ACCESSIBILITY_AUTO; + final boolean oldIncludeForAccessibility = maySkipNotify && includeForAccessibility(); mPrivateFlags2 &= ~PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; - notifyAccessibilityStateChanged(); + if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + } else { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } } } @@ -6868,18 +7144,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public boolean isImportantForAccessibility() { final int mode = (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK) >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; - switch (mode) { - case IMPORTANT_FOR_ACCESSIBILITY_YES: - return true; - case IMPORTANT_FOR_ACCESSIBILITY_NO: + if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO + || mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + return false; + } + + // Check parent mode to ensure we're not hidden. + ViewParent parent = mParent; + while (parent instanceof View) { + if (((View) parent).getImportantForAccessibility() + == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { return false; - case IMPORTANT_FOR_ACCESSIBILITY_AUTO: - return isActionableForAccessibility() || hasListenersForAccessibility() - || getAccessibilityNodeProvider() != null; - default: - throw new IllegalArgumentException("Unknow important for accessibility mode: " - + mode); + } + parent = parent.getParent(); } + + return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility() + || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null + || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE; } /** @@ -6926,6 +7208,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public boolean includeForAccessibility() { + //noinspection SimplifiableIfStatement if (mAttachInfo != null) { return (mAttachInfo.mAccessibilityFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0 @@ -6961,39 +7244,62 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Notifies accessibility services that some view's important for - * accessibility state has changed. Note that such notifications - * are made at most once every + * Notifies that the accessibility state of this view changed. The change + * is local to this view and does not represent structural changes such + * as children and parent. For example, the view became focusable. The + * notification is at at most once every * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()} - * to avoid unnecessary load to the system. Also once a view has - * made a notifucation this method is a NOP until the notification has - * been sent to clients. + * to avoid unnecessary load to the system. Also once a view has a pending + * notifucation this method is a NOP until the notification has been sent. * * @hide + */ + public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) { + if (!AccessibilityManager.getInstance(mContext).isEnabled()) { + return; + } + if (mSendViewStateChangedAccessibilityEvent == null) { + mSendViewStateChangedAccessibilityEvent = + new SendViewStateChangedAccessibilityEvent(); + } + mSendViewStateChangedAccessibilityEvent.runOrPost(changeType); + } + + /** + * Notifies that the accessibility state of this view changed. The change + * is *not* local to this view and does represent structural changes such + * as children and parent. For example, the view size changed. The + * notification is at at most once every + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()} + * to avoid unnecessary load to the system. Also once a view has a pending + * notifucation this method is a NOP until the notification has been sent. * - * TODO: Makse sure this method is called for any view state change - * that is interesting for accessilility purposes. + * @hide */ - public void notifyAccessibilityStateChanged() { + public void notifySubtreeAccessibilityStateChangedIfNeeded() { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return; } - if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_STATE_CHANGED) == 0) { - mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_STATE_CHANGED; + if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { + mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; if (mParent != null) { - mParent.childAccessibilityStateChanged(this); + try { + mParent.notifySubtreeAccessibilityStateChanged( + this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } } } } /** - * Reset the state indicating the this view has requested clients - * interested in its accessibility state to be notified. - * - * @hide + * Reset the flag indicating the accessibility state of the subtree rooted + * at this view changed. */ - public void resetAccessibilityStateChanged() { - mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_STATE_CHANGED; + void resetSubtreeAccessibilityStateChanged() { + mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } /** @@ -7106,7 +7412,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || getAccessibilitySelectionEnd() != end) && (start == end)) { setAccessibilitySelection(start, end); - notifyAccessibilityStateChanged(); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); return true; } } break; @@ -7261,7 +7568,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void dispatchStartTemporaryDetach() { - clearAccessibilityFocus(); clearDisplayList(); onStartTemporaryDetach(); @@ -7497,8 +7803,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return True if the event was handled by the view, false otherwise. */ protected boolean dispatchHoverEvent(MotionEvent event) { - //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; + //noinspection SimplifiableIfStatement if (li != null && li.mOnHoverListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnHoverListener.onHover(this, event)) { @@ -7864,21 +8170,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public boolean onKeyDown(int keyCode, KeyEvent event) { boolean result = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_ENTER: { - if ((mViewFlags & ENABLED_MASK) == DISABLED) { - return true; - } - // Long clickable items don't necessarily have to be clickable - if (((mViewFlags & CLICKABLE) == CLICKABLE || - (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) && - (event.getRepeatCount() == 0)) { - setPressed(true); - checkForLongClick(0); - return true; - } - break; + if (KeyEvent.isConfirmKey(keyCode)) { + if ((mViewFlags & ENABLED_MASK) == DISABLED) { + return true; + } + // Long clickable items don't necessarily have to be clickable + if (((mViewFlags & CLICKABLE) == CLICKABLE || + (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) && + (event.getRepeatCount() == 0)) { + setPressed(true); + checkForLongClick(0); + return true; } } return result; @@ -7910,28 +8212,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param event The KeyEvent object that defines the button action. */ public boolean onKeyUp(int keyCode, KeyEvent event) { - boolean result = false; - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_ENTER: { - if ((mViewFlags & ENABLED_MASK) == DISABLED) { - return true; - } - if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) { - setPressed(false); - - if (!mHasPerformedLongPress) { - // This is a tap, so remove the longpress check - removeLongPressCallback(); + if (KeyEvent.isConfirmKey(keyCode)) { + if ((mViewFlags & ENABLED_MASK) == DISABLED) { + return true; + } + if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) { + setPressed(false); - result = performClick(); - } + if (!mHasPerformedLongPress) { + // This is a tap, so remove the longpress check + removeLongPressCallback(); + return performClick(); } - break; } } - return result; + return false; } /** @@ -8295,6 +8590,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Implement this method to handle touch screen motion events. + *

      + * 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: + *

        + *
      • obeying click sound preferences + *
      • dispatching OnClickListener calls + *
      • handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when + * accessibility features are enabled + *
      * * @param event The motion event. * @return True if the event was handled, false otherwise. @@ -8527,6 +8833,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param mask Constant indicating the bit range that should be changed */ void setFlags(int flags, int mask) { + final boolean accessibilityEnabled = + AccessibilityManager.getInstance(mContext).isEnabled(); + final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility(); + int old = mViewFlags; mViewFlags = (mViewFlags & ~mask) | (flags & mask); @@ -8551,12 +8861,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ if (mParent != null) mParent.focusableViewAvailable(this); } - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } } - if ((flags & VISIBILITY_MASK) == VISIBLE) { + final int newVisibility = flags & VISIBILITY_MASK; + if (newVisibility == VISIBLE) { if ((changed & VISIBILITY_MASK) != 0) { /* * If this view is becoming visible, invalidate it in case it changed while @@ -8609,10 +8917,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ mPrivateFlags |= PFLAG_DRAWN; - if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) { + if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) { // root view becoming invisible shouldn't clear focus and accessibility focus if (getRootView() != this) { - clearFocus(); + if (hasFocus()) clearFocus(); clearAccessibilityFocus(); } } @@ -8622,14 +8930,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if ((changed & VISIBILITY_MASK) != 0) { + // If the view is invisible, cleanup its display list to free up resources + if (newVisibility != VISIBLE) { + cleanupDraw(); + } + if (mParent instanceof ViewGroup) { ((ViewGroup) mParent).onChildVisibilityChanged(this, - (changed & VISIBILITY_MASK), (flags & VISIBILITY_MASK)); + (changed & VISIBILITY_MASK), newVisibility); ((View) mParent).invalidate(true); } else if (mParent != null) { mParent.invalidateChild(this, null); } - dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK)); + dispatchVisibilityChanged(this, newVisibility); } if ((changed & WILL_NOT_CACHE_DRAWING) != 0) { @@ -8668,19 +8981,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - if (AccessibilityManager.getInstance(mContext).isEnabled() - && ((changed & FOCUSABLE) != 0 || (changed & CLICKABLE) != 0 - || (changed & LONG_CLICKABLE) != 0 || (changed & ENABLED) != 0)) { - notifyAccessibilityStateChanged(); + if (accessibilityEnabled) { + if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0 + || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0) { + if (oldIncludeForAccessibility != includeForAccessibility()) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + } else { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + } else if ((changed & ENABLED_MASK) != 0) { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } } } /** * Change the view's z order in the tree, so it's on top of other sibling * views. This ordering change may affect layout, if the parent container - * uses an order-dependent layout scheme (e.g., LinearLayout). This + * uses an order-dependent layout scheme (e.g., LinearLayout). Prior + * to {@link android.os.Build.VERSION_CODES#KITKAT} this * method should be followed by calls to {@link #requestLayout()} and - * {@link View#invalidate()} on the parent. + * {@link View#invalidate()} on the view's parent to force the parent to redraw + * with the new child ordering. * * @see ViewGroup#bringChildToFront(View) */ @@ -9378,9 +9702,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setPivotX(float pivotX) { ensureTransformationInfo(); - mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET; final TransformationInfo info = mTransformationInfo; - if (info.mPivotX != pivotX) { + boolean pivotSet = (mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == + PFLAG_PIVOT_EXPLICITLY_SET; + if (info.mPivotX != pivotX || !pivotSet) { + mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET; invalidateViewProperty(true, false); info.mPivotX = pivotX; info.mMatrixDirty = true; @@ -9428,9 +9754,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setPivotY(float pivotY) { ensureTransformationInfo(); - mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET; final TransformationInfo info = mTransformationInfo; - if (info.mPivotY != pivotY) { + boolean pivotSet = (mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == + PFLAG_PIVOT_EXPLICITLY_SET; + if (info.mPivotY != pivotY || !pivotSet) { + mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET; invalidateViewProperty(true, false); info.mPivotY = pivotY; info.mMatrixDirty = true; @@ -9458,15 +9786,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns whether this View has content which overlaps. This function, intended to be - * overridden by specific View types, is an optimization when alpha is set on a view. If - * rendering overlaps in a view with alpha < 1, that view is drawn to an offscreen buffer - * and then composited it into place, which can be expensive. If the view has no overlapping - * rendering, the view can draw each primitive with the appropriate alpha value directly. - * An example of overlapping rendering is a TextView with a background image, such as a - * Button. An example of non-overlapping rendering is a TextView with no background, or - * an ImageView with only the foreground image. The default implementation returns true; - * subclasses should override if they have cases which can be optimized. + * Returns whether this View has content which overlaps. + * + *

      This function, intended to be overridden by specific View types, is an optimization when + * alpha is set on a view. If rendering overlaps in a view with alpha < 1, that view is drawn to + * an offscreen buffer and then composited into place, which can be expensive. If the view has + * no overlapping rendering, the view can draw each primitive with the appropriate alpha value + * directly. An example of overlapping rendering is a TextView with a background image, such as + * a Button. An example of non-overlapping rendering is a TextView with no background, or an + * ImageView with only the foreground image. The default implementation returns true; subclasses + * should override if they have cases which can be optimized.

      + * + *

      The current implementation of the saveLayer and saveLayerAlpha methods in {@link Canvas} + * necessitates that a View return true if it uses the methods internally without passing the + * {@link Canvas#CLIP_TO_LAYER_SAVE_FLAG}.

      * * @return true if the content in this view might overlap, false otherwise. */ @@ -9515,7 +9848,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_ALPHA_SET; invalidateViewProperty(true, false); if (mDisplayList != null) { - mDisplayList.setAlpha(alpha); + mDisplayList.setAlpha(getFinalAlpha()); } } } @@ -9542,13 +9875,58 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { mPrivateFlags &= ~PFLAG_ALPHA_SET; if (mDisplayList != null) { - mDisplayList.setAlpha(alpha); + mDisplayList.setAlpha(getFinalAlpha()); } } } return false; } + /** + * This property is hidden and intended only for use by the Fade transition, which + * animates it to produce a visual translucency that does not side-effect (or get + * affected by) the real alpha property. This value is composited with the other + * alpha value (and the AlphaAnimation value, when that is present) to produce + * a final visual translucency result, which is what is passed into the DisplayList. + * + * @hide + */ + public void setTransitionAlpha(float alpha) { + ensureTransformationInfo(); + if (mTransformationInfo.mTransitionAlpha != alpha) { + mTransformationInfo.mTransitionAlpha = alpha; + mPrivateFlags &= ~PFLAG_ALPHA_SET; + invalidateViewProperty(true, false); + if (mDisplayList != null) { + mDisplayList.setAlpha(getFinalAlpha()); + } + } + } + + /** + * Calculates the visual alpha of this view, which is a combination of the actual + * alpha value and the transitionAlpha value (if set). + */ + private float getFinalAlpha() { + if (mTransformationInfo != null) { + return mTransformationInfo.mAlpha * mTransformationInfo.mTransitionAlpha; + } + return 1; + } + + /** + * This property is hidden and intended only for use by the Fade transition, which + * animates it to produce a visual translucency that does not side-effect (or get + * affected by) the real alpha property. This value is composited with the other + * alpha value (and the AlphaAnimation value, when that is present) to produce + * a final visual translucency result, which is what is passed into the DisplayList. + * + * @hide + */ + public float getTransitionAlpha() { + return mTransformationInfo != null ? mTransformationInfo.mTransitionAlpha : 1; + } + /** * Top position of this view relative to its parent. * @@ -9981,8 +10359,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * is inside the view, where the area of the view is expanded by the slop factor. * This method is called while processing touch-move events to determine if the event * is still within the view. + * + * @hide */ - private boolean pointInView(float localX, float localY, float slop) { + public boolean pointInView(float localX, float localY, float slop) { return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && localY < ((mBottom - mTop) + slop); } @@ -10640,7 +11020,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(category = "drawing") public boolean isOpaque() { return (mPrivateFlags & PFLAG_OPAQUE_MASK) == PFLAG_OPAQUE_MASK && - ((mTransformationInfo != null ? mTransformationInfo.mAlpha : 1.0f) >= 1.0f); + getFinalAlpha() >= 1.0f; } /** @@ -11715,9 +12095,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH; } + mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT; + jumpDrawablesToCurrentState(); - clearAccessibilityFocus(); + resetSubtreeAccessibilityStateChanged(); + if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); @@ -11855,10 +12238,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!canResolveLayoutDirection()) return false; // Parent has not yet resolved, LTR is still the default - if (!mParent.isLayoutDirectionResolved()) return false; + try { + if (!mParent.isLayoutDirectionResolved()) return false; - if (mParent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) { - mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL; + if (mParent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) { + mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL; + } + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); } break; case LAYOUT_DIRECTION_RTL: @@ -11884,13 +12272,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Check if layout direction resolution can be done. * * @return true if layout direction resolution can be done otherwise return false. - * - * @hide */ public boolean canResolveLayoutDirection() { switch (getRawLayoutDirection()) { case LAYOUT_DIRECTION_INHERIT: - return (mParent != null) && mParent.canResolveLayoutDirection(); + if (mParent != null) { + try { + return mParent.canResolveLayoutDirection(); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + return false; + default: return true; } @@ -11918,7 +12313,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @return true if layout direction has been resolved. - * @hide */ public boolean isLayoutDirectionResolved() { return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED; @@ -11934,17 +12328,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Resolve padding depending on layout direction. + * Resolves padding depending on layout direction, if applicable, and + * recomputes internal padding values to adjust for scroll bars. * * @hide */ public void resolvePadding() { + final int resolvedLayoutDirection = getLayoutDirection(); + if (!isRtlCompatibilityMode()) { // Post Jelly Bean MR1 case: we need to take the resolved layout direction into account. // If start / end padding are defined, they will be resolved (hence overriding) to // left / right or right / left depending on the resolved layout direction. // If start / end padding are not defined, use the left / right ones. - int resolvedLayoutDirection = getLayoutDirection(); switch (resolvedLayoutDirection) { case LAYOUT_DIRECTION_RTL: if (mUserPaddingStart != UNDEFINED_PADDING) { @@ -11973,12 +12369,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom; - - internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, - mUserPaddingBottom); - onRtlPropertiesChanged(resolvedLayoutDirection); } + internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom); + onRtlPropertiesChanged(resolvedLayoutDirection); + mPrivateFlags2 |= PFLAG2_PADDING_RESOLVED; } @@ -11999,6 +12394,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void onDetachedFromWindow() { mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; + mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT; removeUnsetPressCallback(); removeLongPressCallback(); @@ -12006,9 +12402,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeSendViewScrolledAccessibilityEventCallback(); destroyDrawingCache(); - destroyLayer(false); + cleanupDraw(); + + mCurrentAnimation = null; + } + + private void cleanupDraw() { if (mAttachInfo != null) { if (mDisplayList != null) { mDisplayList.markDirty(); @@ -12017,12 +12418,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mViewRootImpl.cancelInvalidate(this); } else { // Should never happen - clearDisplayList(); + resetDisplayList(); } + } - mCurrentAnimation = null; + /** + * This method ensures the hardware renderer is in a valid state + * before executing the specified action. + * + * This method will attempt to set a valid state even if the window + * the renderer is attached to was destroyed. + * + * This method is not guaranteed to work. If the hardware renderer + * does not exist or cannot be put in a valid state, this method + * will not executed the specified action. + * + * The specified action is executed synchronously. + * + * @param action The action to execute after the renderer is in a valid state + * + * @return True if the specified Runnable was executed, false otherwise + * + * @hide + */ + public boolean executeHardwareAction(Runnable action) { + //noinspection SimplifiableIfStatement + if (mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { + return mAttachInfo.mHardwareRenderer.safelyRun(action); + } + return false; + } - resetAccessibilityStateChanged(); + void invalidateInheritedLayoutMode(int layoutModeOfRoot) { } /** @@ -12184,6 +12611,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Cancel any deferred high-level input events that were previously posted to the event queue. + * + *

      Many views post high-level events such as click handlers to the event queue + * to run deferred in order to preserve a desired user experience - clearing visible + * pressed states before executing, etc. This method will abort any events of this nature + * that are currently in flight.

      + * + *

      Custom views that generate their own high-level deferred input events should override + * {@link #onCancelPendingInputEvents()} and remove those pending events from the queue.

      + * + *

      This will also cancel pending input events for any child views.

      + * + *

      Note that this may not be sufficient as a debouncing strategy for clicks in all cases. + * This will not impact newer events posted after this call that may occur as a result of + * lower-level input events still waiting in the queue. If you are trying to prevent + * double-submitted events for the duration of some sort of asynchronous transaction + * you should also take other steps to protect against unexpected double inputs e.g. calling + * {@link #setEnabled(boolean) setEnabled(false)} and re-enabling the view when + * the transaction completes, tracking already submitted transaction IDs, etc.

      + */ + public final void cancelPendingInputEvents() { + dispatchCancelPendingInputEvents(); + } + + /** + * Called by {@link #cancelPendingInputEvents()} to cancel input events in flight. + * Overridden by ViewGroup to dispatch. Package scoped to prevent app-side meddling. + */ + void dispatchCancelPendingInputEvents() { + mPrivateFlags3 &= ~PFLAG3_CALLED_SUPER; + onCancelPendingInputEvents(); + if ((mPrivateFlags3 & PFLAG3_CALLED_SUPER) != PFLAG3_CALLED_SUPER) { + throw new SuperNotCalledException("View " + getClass().getSimpleName() + + " did not call through to super.onCancelPendingInputEvents()"); + } + } + + /** + * Called as the result of a call to {@link #cancelPendingInputEvents()} on this view or + * a parent view. + * + *

      This method is responsible for removing any pending high-level input events that were + * posted to the event queue to run later. Custom view classes that post their own deferred + * high-level events via {@link #post(Runnable)}, {@link #postDelayed(Runnable, long)} or + * {@link android.os.Handler} should override this method, call + * super.onCancelPendingInputEvents() and remove those callbacks as appropriate. + *

      + */ + public void onCancelPendingInputEvents() { + removePerformClickCallback(); + cancelLongPress(); + mPrivateFlags3 |= PFLAG3_CALLED_SUPER; + } + /** * Store this view hierarchy's frozen state into the given container. * @@ -12519,16 +13001,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void buildLayer() { if (mLayerType == LAYER_TYPE_NONE) return; - if (mAttachInfo == null) { + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo == null) { throw new IllegalStateException("This view must be attached to a window first"); } switch (mLayerType) { case LAYER_TYPE_HARDWARE: - if (mAttachInfo.mHardwareRenderer != null && - mAttachInfo.mHardwareRenderer.isEnabled() && - mAttachInfo.mHardwareRenderer.validate()) { + if (attachInfo.mHardwareRenderer != null && + attachInfo.mHardwareRenderer.isEnabled() && + attachInfo.mHardwareRenderer.validate()) { getHardwareLayer(); + // TODO: We need a better way to handle this case + // If views have registered pre-draw listeners they need + // to be notified before we build the layer. Those listeners + // may however rely on other events to happen first so we + // cannot just invoke them here until they don't cancel the + // current frame + if (!attachInfo.mTreeObserver.hasOnPreDrawListeners()) { + attachInfo.mViewRootImpl.dispatchFlushHardwareLayerUpdates(); + } } break; case LAYER_TYPE_SOFTWARE: @@ -12614,12 +13106,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (info != null && info.mHardwareRenderer != null && info.mHardwareRenderer.isEnabled() && (valid || info.mHardwareRenderer.validate())) { + + info.mHardwareRenderer.cancelLayerUpdate(mHardwareLayer); mHardwareLayer.destroy(); mHardwareLayer = null; - if (mDisplayList != null) { - mDisplayList.reset(); - } invalidate(true); invalidateParentCaches(); } @@ -12640,6 +13131,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ protected void destroyHardwareResources() { + resetDisplayList(); destroyLayer(true); } @@ -12784,8 +13276,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mRecreateDisplayList = true; } if (displayList == null) { - final String name = getClass().getSimpleName(); - displayList = mAttachInfo.mHardwareRenderer.createDisplayList(name); + displayList = mAttachInfo.mHardwareRenderer.createDisplayList(getClass().getName()); // If we're creating a new display list, make sure our parent gets invalidated // since they will need to recreate their display list to account for this // new child display list. @@ -12888,6 +13379,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private void resetDisplayList() { + if (mDisplayList != null) { + mDisplayList.reset(); + } + } + /** *

      Calling this method is equivalent to calling getDrawingCache(false).

      * @@ -13060,14 +13557,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; @@ -13222,6 +13713,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); + if (mOverlay != null && !mOverlay.isEmpty()) { + mOverlay.getOverlayView().draw(canvas); + } } else { draw(canvas); } @@ -13432,7 +13926,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onAnimationStart(); } - boolean more = a.getTransformation(drawingTime, parent.mChildTransformation, 1f); + final Transformation t = parent.getChildTransformation(); + boolean more = a.getTransformation(drawingTime, t, 1f); if (scalingRequired && mAttachInfo.mApplicationScale != 1f) { if (parent.mInvalidationTransformation == null) { parent.mInvalidationTransformation = new Transformation(); @@ -13440,7 +13935,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidationTransform = parent.mInvalidationTransformation; a.getTransformation(drawingTime, invalidationTransform, 1f); } else { - invalidationTransform = parent.mChildTransformation; + invalidationTransform = t; } if (more) { @@ -13493,23 +13988,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { ViewGroup parentVG = (ViewGroup) mParent; - final boolean hasTransform = - parentVG.getChildStaticTransformation(this, parentVG.mChildTransformation); - if (hasTransform) { - Transformation transform = parentVG.mChildTransformation; - final int transformType = parentVG.mChildTransformation.getTransformationType(); + final Transformation t = parentVG.getChildTransformation(); + if (parentVG.getChildStaticTransformation(this, t)) { + final int transformType = t.getTransformationType(); if (transformType != Transformation.TYPE_IDENTITY) { if ((transformType & Transformation.TYPE_ALPHA) != 0) { - alpha = transform.getAlpha(); + alpha = t.getAlpha(); } if ((transformType & Transformation.TYPE_MATRIX) != 0) { - displayList.setMatrix(transform.getMatrix()); + displayList.setMatrix(t.getMatrix()); } } } } if (mTransformationInfo != null) { - alpha *= mTransformationInfo.mAlpha; + alpha *= getFinalAlpha(); if (alpha < 1) { final int multipliedAlpha = (int) (255 * alpha); if (onSetAlpha(multipliedAlpha)) { @@ -13548,7 +14041,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int flags = parent.mGroupFlags; if ((flags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) == ViewGroup.FLAG_CLEAR_TRANSFORMATION) { - parent.mChildTransformation.clear(); + parent.getChildTransformation().clear(); parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION; } @@ -13576,7 +14069,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } - transformToApply = parent.mChildTransformation; + transformToApply = parent.getChildTransformation(); } else { if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) == PFLAG3_VIEW_IS_ANIMATING_TRANSFORM && mDisplayList != null) { @@ -13586,12 +14079,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (!useDisplayListProperties && (flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { - final boolean hasTransform = - parent.getChildStaticTransformation(this, parent.mChildTransformation); + final Transformation t = parent.getChildTransformation(); + final boolean hasTransform = parent.getChildStaticTransformation(this, t); if (hasTransform) { - final int transformType = parent.mChildTransformation.getTransformationType(); - transformToApply = transformType != Transformation.TYPE_IDENTITY ? - parent.mChildTransformation : null; + final int transformType = t.getTransformationType(); + transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null; concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0; } } @@ -13699,8 +14191,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - float alpha = useDisplayListProperties ? 1 : getAlpha(); - if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() || + float alpha = useDisplayListProperties ? 1 : (getAlpha() * getTransitionAlpha()); + if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) { if (transformToApply != null || !childHasIdentityMatrix) { int transX = 0; @@ -13757,7 +14249,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; } if (useDisplayListProperties) { - displayList.setAlpha(alpha * getAlpha()); + displayList.setAlpha(alpha * getAlpha() * getTransitionAlpha()); } else if (layerType == LAYER_TYPE_NONE) { final int scrollX = hasDisplayList ? 0 : sx; final int scrollY = hasDisplayList ? 0 : sy; @@ -13867,10 +14359,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (more && hardwareAccelerated) { - // invalidation is the trigger to recreate display lists, so if we're using - // display lists to render, force an invalidate to allow the animation to - // continue drawing another frame - parent.invalidate(true); if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { // alpha animations should cause the child to recreate its display list invalidate(true); @@ -14280,12 +14768,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @SuppressWarnings({"unchecked"}) public void layout(int l, int t, int r, int b) { + if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { + onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); + mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; + } + int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; + boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); + if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; @@ -14300,7 +14795,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } + mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; + mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; } /** @@ -14393,6 +14890,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= drawn; mBackgroundSizeChanged = true; + + notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; } @@ -14503,13 +15002,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ protected void resolveDrawables() { - if (canResolveLayoutDirection()) { - if (mBackground != null) { - mBackground.setLayoutDirection(getLayoutDirection()); - } - mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED; - onResolveDrawables(getLayoutDirection()); + // Drawables resolution may need to happen before resolving the layout direction (which is + // done only during the measure() call). + // If the layout direction is not resolved yet, we cannot resolve the Drawables except in + // one case: when the raw layout direction has not been defined as LAYOUT_DIRECTION_INHERIT. + // So, if the raw layout direction is LAYOUT_DIRECTION_LTR or LAYOUT_DIRECTION_RTL or + // LAYOUT_DIRECTION_LOCALE, we can "cheat" and we don't need to wait for the layout + // direction to be resolved as its resolved value will be the same as its raw value. + if (!isLayoutDirectionResolved() && + getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT) { + return; } + + final int layoutDirection = isLayoutDirectionResolved() ? + getLayoutDirection() : getRawLayoutDirection(); + + if (mBackground != null) { + mBackground.setLayoutDirection(layoutDirection); + } + mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED; + onResolveDrawables(layoutDirection); } /** @@ -15151,15 +15663,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param selected true if the view must be selected, false otherwise */ public void setSelected(boolean selected) { + //noinspection DoubleNegation if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) { mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0); if (!selected) resetPressedState(); invalidate(true); refreshDrawableState(); dispatchSetSelected(selected); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -15197,6 +15709,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param activated true if the view must be activated, false otherwise */ public void setActivated(boolean activated) { + //noinspection DoubleNegation if (((mPrivateFlags & PFLAG_ACTIVATED) != 0) != activated) { mPrivateFlags = (mPrivateFlags & ~PFLAG_ACTIVATED) | (activated ? PFLAG_ACTIVATED : 0); invalidate(true); @@ -15269,6 +15782,90 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return parent; } + /** + * Transforms a motion event from view-local coordinates to on-screen + * coordinates. + * + * @param ev the view-local motion event + * @return false if the transformation could not be applied + * @hide + */ + public boolean toGlobalMotionEvent(MotionEvent ev) { + final AttachInfo info = mAttachInfo; + if (info == null) { + return false; + } + + transformMotionEventToGlobal(ev); + ev.offsetLocation(info.mWindowLeft, info.mWindowTop); + return true; + } + + /** + * Transforms a motion event from on-screen coordinates to view-local + * coordinates. + * + * @param ev the on-screen motion event + * @return false if the transformation could not be applied + * @hide + */ + public boolean toLocalMotionEvent(MotionEvent ev) { + final AttachInfo info = mAttachInfo; + if (info == null) { + return false; + } + + ev.offsetLocation(-info.mWindowLeft, -info.mWindowTop); + transformMotionEventToLocal(ev); + return true; + } + + /** + * Recursive helper method that applies transformations in post-order. + * + * @param ev the on-screen motion event + */ + private void transformMotionEventToLocal(MotionEvent ev) { + final ViewParent parent = mParent; + if (parent instanceof View) { + final View vp = (View) parent; + vp.transformMotionEventToLocal(ev); + ev.offsetLocation(vp.mScrollX, vp.mScrollY); + } else if (parent instanceof ViewRootImpl) { + final ViewRootImpl vr = (ViewRootImpl) parent; + ev.offsetLocation(0, vr.mCurScrollY); + } + + ev.offsetLocation(-mLeft, -mTop); + + if (!hasIdentityMatrix()) { + ev.transform(getInverseMatrix()); + } + } + + /** + * Recursive helper method that applies transformations in pre-order. + * + * @param ev the on-screen motion event + */ + private void transformMotionEventToGlobal(MotionEvent ev) { + if (!hasIdentityMatrix()) { + ev.transform(getMatrix()); + } + + ev.offsetLocation(mLeft, mTop); + + final ViewParent parent = mParent; + if (parent instanceof View) { + final View vp = (View) parent; + ev.offsetLocation(-vp.mScrollX, -vp.mScrollY); + vp.transformMotionEventToGlobal(ev); + } else if (parent instanceof ViewRootImpl) { + final ViewRootImpl vr = (ViewRootImpl) parent; + ev.offsetLocation(0, -vr.mCurScrollY); + } + } + /** *

      Computes the coordinates of this view on the screen. The argument * must be an array of two integers. After the method returns, the array @@ -15637,7 +16234,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private void setKeyedTag(int key, Object tag) { if (mKeyedTags == null) { - mKeyedTags = new SparseArray(); + mKeyedTags = new SparseArray(2); } mKeyedTags.put(key, tag); @@ -15774,6 +16371,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * handle possible request-during-layout errors correctly.

      */ public void requestLayout() { + if (mMeasureCache != null) mMeasureCache.clear(); + if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy @@ -15803,6 +16402,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * on the parent. */ public void forceLayout() { + if (mMeasureCache != null) mMeasureCache.clear(); + mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; } @@ -15836,6 +16437,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); } + + // Suppress sign extension for the low bytes + long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; + if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); + if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { @@ -15845,8 +16451,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, resolveRtlPropertiesIfNeeded(); - // measure ourselves, this should set the measured dimension flag back - onMeasure(widthMeasureSpec, heightMeasureSpec); + int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : + mMeasureCache.indexOfKey(key); + if (cacheIndex < 0 || sIgnoreMeasureCache) { + // measure ourselves, this should set the measured dimension flag back + onMeasure(widthMeasureSpec, heightMeasureSpec); + mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; + } else { + long value = mMeasureCache.valueAt(cacheIndex); + // Casting a long to int drops the high 32 bits, no mask needed + setMeasuredDimension((int) (value >> 32), (int) value); + mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; + } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer @@ -15861,6 +16477,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; + + mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | + (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension } /** @@ -16352,7 +16971,8 @@ 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}, - * and {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}. + * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE}, + * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}. */ public void setSystemUiVisibility(int visibility) { if (visibility != mSystemUiVisibility) { @@ -16364,11 +16984,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns the last {@link #setSystemUiVisibility(int) that this view has requested. + * Returns the last {@link #setSystemUiVisibility(int)} that this view has requested. * @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}, - * and {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}. + * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE}, + * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}. */ public int getSystemUiVisibility() { return mSystemUiVisibility; @@ -16387,7 +17008,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Override to find out when the window's requested system UI visibility * has changed, that is the value returned by {@link #getWindowSystemUiVisibility()}. - * This is different from the callbacks recieved through + * This is different from the callbacks received through * {@link #setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener)} * in that this is only telling you about the local request of the window, * not the actual values applied by the system. @@ -16706,8 +17327,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, *

      */ public boolean dispatchDragEvent(DragEvent event) { - //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; + //noinspection SimplifiableIfStatement if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnDragListener.onDrag(this, event)) { return true; @@ -17072,14 +17693,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } // Parent has not yet resolved, so we still return the default - if (!mParent.isTextDirectionResolved()) { - mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; - // Resolution will need to happen again later - return false; + try { + if (!mParent.isTextDirectionResolved()) { + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; + // Resolution will need to happen again later + return false; + } + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED | + PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; + return true; } // Set current resolved direction to the same value as the parent's one - final int parentResolvedDirection = mParent.getTextDirection(); + int parentResolvedDirection; + try { + parentResolvedDirection = mParent.getTextDirection(); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + parentResolvedDirection = TEXT_DIRECTION_LTR; + } switch (parentResolvedDirection) { case TEXT_DIRECTION_FIRST_STRONG: case TEXT_DIRECTION_ANY_RTL: @@ -17120,13 +17756,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Check if text direction resolution can be done. * * @return true if text direction resolution can be done otherwise return false. - * - * @hide */ public boolean canResolveTextDirection() { switch (getRawTextDirection()) { case TEXT_DIRECTION_INHERIT: - return (mParent != null) && mParent.canResolveTextDirection(); + if (mParent != null) { + try { + return mParent.canResolveTextDirection(); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + return false; + default: return true; } @@ -17156,8 +17799,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @return true if text direction is resolved. - * - * @hide */ public boolean isTextDirectionResolved() { return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED; @@ -17284,13 +17925,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } // Parent has not yet resolved, so we still return the default - if (!mParent.isTextAlignmentResolved()) { - mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; - // Resolution will need to happen again later - return false; + try { + if (!mParent.isTextAlignmentResolved()) { + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; + // Resolution will need to happen again later + return false; + } + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED | + PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; + return true; } - final int parentResolvedTextAlignment = mParent.getTextAlignment(); + int parentResolvedTextAlignment; + try { + parentResolvedTextAlignment = mParent.getTextAlignment(); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + parentResolvedTextAlignment = TEXT_ALIGNMENT_GRAVITY; + } switch (parentResolvedTextAlignment) { case TEXT_ALIGNMENT_GRAVITY: case TEXT_ALIGNMENT_TEXT_START: @@ -17335,13 +17991,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Check if text alignment resolution can be done. * * @return true if text alignment resolution can be done otherwise return false. - * - * @hide */ public boolean canResolveTextAlignment() { switch (getRawTextAlignment()) { case TEXT_DIRECTION_INHERIT: - return (mParent != null) && mParent.canResolveTextAlignment(); + if (mParent != null) { + try { + return mParent.canResolveTextAlignment(); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + return false; + default: return true; } @@ -17371,8 +18034,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @return true if text alignment is resolved. - * - * @hide */ public boolean isTextAlignmentResolved() { return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED; @@ -18115,6 +18776,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets = new ViewTreeObserver.InternalInsetsInfo(); + /** + * Set to true when mGivenInternalInsets is non-empty. + */ + boolean mHasNonEmptyGivenInternalInsets; + /** * All views in the window's hierarchy that serve as scroll containers, * used to determine if the window can be resized or must be panned @@ -18749,6 +19415,79 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private class SendViewStateChangedAccessibilityEvent implements Runnable { + private int mChangeTypes = 0; + private boolean mPosted; + private boolean mPostedWithDelay; + private long mLastEventTimeMillis; + + @Override + public void run() { + mPosted = false; + mPostedWithDelay = false; + mLastEventTimeMillis = SystemClock.uptimeMillis(); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(mChangeTypes); + sendAccessibilityEventUnchecked(event); + } + mChangeTypes = 0; + } + + public void runOrPost(int changeType) { + mChangeTypes |= changeType; + + // If this is a live region or the child of a live region, collect + // all events from this frame and send them on the next frame. + if (inLiveRegion()) { + // If we're already posted with a delay, remove that. + if (mPostedWithDelay) { + removeCallbacks(this); + mPostedWithDelay = false; + } + // Only post if we're not already posted. + if (!mPosted) { + post(this); + mPosted = true; + } + return; + } + + if (mPosted) { + return; + } + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; + final long minEventIntevalMillis = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastMillis >= minEventIntevalMillis) { + removeCallbacks(this); + run(); + } else { + postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); + mPosted = true; + mPostedWithDelay = true; + } + } + } + + private boolean inLiveRegion() { + if (getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) { + return true; + } + + ViewParent parent = getParent(); + while (parent instanceof View) { + if (((View) parent).getAccessibilityLiveRegion() + != View.ACCESSIBILITY_LIVE_REGION_NONE) { + return true; + } + parent = parent.getParent(); + } + + return false; + } + /** * Dump all private flags in readable format, useful for documentation and * sanity checking. diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 499075e1b6a1ffd0ae666aaeb57159ac09d26963..c3f064fdfd4ae8bfa4742b7f91e2f2dcbeff2dd9 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -96,6 +96,13 @@ public class ViewConfiguration { */ private static final int DOUBLE_TAP_TIMEOUT = 300; + /** + * Defines the minimum duration in milliseconds between the first tap's up event and + * the second tap's down event for an interaction to be considered a + * double-tap. + */ + private static final int DOUBLE_TAP_MIN_TIME = 40; + /** * Defines the maximum duration in milliseconds between a touch pad * touch and release for a given touch to be considered a tap (click) as @@ -291,7 +298,7 @@ public class ViewConfiguration { if (!sHasPermanentMenuKeySet) { IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); try { - sHasPermanentMenuKey = !wm.hasSystemNavBar() && !wm.hasNavigationBar(); + sHasPermanentMenuKey = !wm.hasNavigationBar(); sHasPermanentMenuKeySet = true; } catch (RemoteException ex) { sHasPermanentMenuKey = false; @@ -435,6 +442,17 @@ public class ViewConfiguration { return DOUBLE_TAP_TIMEOUT; } + /** + * @return the minimum duration in milliseconds between the first tap's + * up event and the second tap's down event for an interaction to be considered a + * double-tap. + * + * @hide + */ + public static int getDoubleTapMinTime() { + return DOUBLE_TAP_MIN_TIME; + } + /** * @return the maximum duration in milliseconds between a touch pad * touch and release for a given touch to be considered a tap (click) as diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index ed128b0731568b235ab94ddbf8730bdb2d0b04e4..92e5e964fd34f0a3d1f51be360eb12eda9744372 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -698,6 +698,11 @@ public class ViewDebug { captureViewLayer(group.getChildAt(i), clientStream, localVisible); } } + + if (view.mOverlay != null) { + ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup; + captureViewLayer(overlayContainer, clientStream, localVisible); + } } private static void outputDisplayList(View root, String parameter) throws IOException { @@ -743,7 +748,7 @@ public class ViewDebug { } } - private static Bitmap performViewCapture(final View captureView, final boolean skpiChildren) { + private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) { if (captureView != null) { final CountDownLatch latch = new CountDownLatch(1); final Bitmap[] cache = new Bitmap[1]; @@ -752,7 +757,7 @@ public class ViewDebug { public void run() { try { cache[0] = captureView.createSnapshot( - Bitmap.Config.ARGB_8888, 0, skpiChildren); + Bitmap.Config.ARGB_8888, 0, skipChildren); } catch (OutOfMemoryError e) { Log.w("View", "Out of memory for bitmap"); } finally { @@ -815,6 +820,13 @@ public class ViewDebug { } else if (isRequestedView(view, className, hashCode)) { return view; } + if (view.mOverlay != null) { + final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup, + className, hashCode); + if (found != null) { + return found; + } + } if (view instanceof HierarchyHandler) { final View found = ((HierarchyHandler)view) .findHierarchyView(className, hashCode); @@ -823,12 +835,19 @@ public class ViewDebug { } } } - return null; } private static boolean isRequestedView(View view, String className, int hashCode) { - return view.getClass().getName().equals(className) && view.hashCode() == hashCode; + if (view.hashCode() == hashCode) { + String viewClassName = view.getClass().getName(); + if (className.equals("ViewOverlay")) { + return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup"); + } else { + return className.equals(viewClassName); + } + } + return false; } private static void dumpViewHierarchy(Context context, ViewGroup group, @@ -850,6 +869,12 @@ public class ViewDebug { } else { dumpView(context, view, out, level + 1, includeProperties); } + if (view.mOverlay != null) { + ViewOverlay overlay = view.getOverlay(); + ViewGroup overlayContainer = overlay.mOverlayViewGroup; + dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren, + includeProperties); + } } if (group instanceof HierarchyHandler) { ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1); @@ -863,7 +888,11 @@ public class ViewDebug { for (int i = 0; i < level; i++) { out.write(' '); } - out.write(view.getClass().getName()); + String className = view.getClass().getName(); + if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) { + className = "ViewOverlay"; + } + out.write(className); out.write('@'); out.write(Integer.toHexString(view.hashCode())); out.write(' '); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 3aa4cfb34374ca01eb2c91be50375068888ff8e2..94142376b2632ddf88d312286cbe9be1b4d83d5f 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -38,6 +38,7 @@ import android.util.Log; import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -130,7 +131,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * A Transformation used when drawing children, to * apply on the child being drawn. */ - final Transformation mChildTransformation = new Transformation(); + private Transformation mChildTransformation; /** * Used to track the current invalidation region. @@ -154,7 +155,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private boolean mChildAcceptsDrag; // Used during drag dispatch - private final PointF mLocalPoint = new PointF(); + private PointF mLocalPoint; // Layout animation private LayoutAnimationController mLayoutAnimationController; @@ -206,7 +207,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}. */ - private int mLayoutMode = DEFAULT_LAYOUT_MODE; + private int mLayoutMode = LAYOUT_MODE_UNDEFINED; /** * NOTE: If you change the flags below make sure to reflect the changes @@ -346,6 +347,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ private static final int FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW = 0x400000; + /** + * When true, indicates that a layoutMode has been explicitly set, either with + * an explicit call to {@link #setLayoutMode(int)} in code or from an XML resource. + * This distinguishes the situation in which a layout mode was inherited from + * one of the ViewGroup's ancestors and cached locally. + */ + private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000; + /** * Indicates which types of drawing caches are to be kept in memory. * This field should be made private, so it is hidden from the SDK. @@ -375,6 +384,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Layout Modes + private static final int LAYOUT_MODE_UNDEFINED = -1; + /** * This constant is a {@link #setLayoutMode(int) layoutMode}. * Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top}, @@ -391,7 +402,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; /** @hide */ - public static int DEFAULT_LAYOUT_MODE = LAYOUT_MODE_CLIP_BOUNDS; + public static int LAYOUT_MODE_DEFAULT = LAYOUT_MODE_CLIP_BOUNDS; /** * We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL @@ -533,7 +544,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } break; case R.styleable.ViewGroup_layoutMode: - setLayoutMode(a.getInt(attr, DEFAULT_LAYOUT_MODE)); + setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED)); break; } } @@ -732,8 +743,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Called when a child view has changed whether or not it is tracking transient state. - * - * @hide */ public void childHasTransientStateChanged(View child, boolean childHasTransientState) { final boolean oldHasTransientState = hasTransientState(); @@ -754,9 +763,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - /** - * @hide - */ @Override public boolean hasTransientState() { return mChildCountWithTransientState > 0 || super.hasTransientState(); @@ -1118,9 +1124,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager removeFromArray(index); addInArray(child, mChildrenCount); child.mParent = this; + requestLayout(); + invalidate(); } } + private PointF getLocalPoint() { + if (mLocalPoint == null) mLocalPoint = new PointF(); + return mLocalPoint; + } + /** * {@inheritDoc} */ @@ -1134,6 +1147,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager ViewRootImpl root = getViewRootImpl(); // Dispatch down the view hierarchy + final PointF localPoint = getLocalPoint(); + switch (event.mAction) { case DragEvent.ACTION_DRAG_STARTED: { // clear state to recalculate which views we drag over @@ -1194,7 +1209,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case DragEvent.ACTION_DRAG_LOCATION: { // Find the [possibly new] drag target - final View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint); + final View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint); // If we've changed apparent drag target, tell the view root which view // we're over now [for purposes of the eventual drag-recipient-changed @@ -1228,8 +1243,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Dispatch the actual drag location notice, localized into its coordinates if (target != null) { - event.mX = mLocalPoint.x; - event.mY = mLocalPoint.y; + event.mX = localPoint.x; + event.mY = localPoint.y; retval = target.dispatchDragEvent(event); @@ -1263,11 +1278,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case DragEvent.ACTION_DROP: { if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, "Drop event: " + event); - View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint); + View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint); if (target != null) { if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, " dispatch drop to " + target); - event.mX = mLocalPoint.x; - event.mY = mLocalPoint.y; + event.mX = localPoint.x; + event.mY = localPoint.y; retval = target.dispatchDragEvent(event); event.mX = tx; event.mY = ty; @@ -1689,16 +1704,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - /** - * @hide - */ - @Override - public void childAccessibilityStateChanged(View child) { - if (mParent != null) { - mParent.childAccessibilityStateChanged(child); - } - } - /** * Implement this method to intercept hover events before they are handled * by child views. @@ -2523,17 +2528,29 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager event.setClassName(ViewGroup.class.getName()); } - /** - * @hide - */ @Override - public void resetAccessibilityStateChanged() { - super.resetAccessibilityStateChanged(); + public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { + // If this is a live region, we should send a subtree change event + // from this view. Otherwise, we can let it propagate up. + if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { + notifyViewAccessibilityStateChangedIfNeeded(changeType); + } else if (mParent != null) { + try { + mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + + @Override + void resetSubtreeAccessibilityStateChanged() { + super.resetSubtreeAccessibilityStateChanged(); View[] children = mChildren; final int childCount = mChildrenCount; for (int i = 0; i < childCount; i++) { - View child = children[i]; - child.resetAccessibilityStateChanged(); + children[i].resetSubtreeAccessibilityStateChanged(); } } @@ -2773,8 +2790,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return (int) (dips * scale + 0.5f); } - private void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint, - int lineLength, int lineWidth) { + private static void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint, + int lineLength, int lineWidth) { drawCorner(canvas, paint, x1, y1, lineLength, lineLength, lineWidth); drawCorner(canvas, paint, x1, y2, lineLength, -lineLength, lineWidth); drawCorner(canvas, paint, x2, y1, -lineLength, lineLength, lineWidth); @@ -3170,6 +3187,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + void dispatchCancelPendingInputEvents() { + super.dispatchCancelPendingInputEvents(); + + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = 0; i < count; i++) { + children[i].dispatchCancelPendingInputEvents(); + } + } + /** * When this property is set to true, this ViewGroup supports static transformations on * children; this causes @@ -3204,6 +3232,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } + Transformation getChildTransformation() { + if (mChildTransformation == null) { + mChildTransformation = new Transformation(); + } + return mChildTransformation; + } + /** * {@hide} */ @@ -3451,6 +3486,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + private void clearCachedLayoutMode() { + if (!hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) { + mLayoutMode = LAYOUT_MODE_UNDEFINED; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + clearCachedLayoutMode(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + clearCachedLayoutMode(); + } + /** * Adds a view during layout. This is useful if in your onLayout() method, * you need to add more views (as does the list view for example). @@ -3565,6 +3618,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (child.hasTransientState()) { childHasTransientStateChanged(child, true); } + + if (child.isImportantForAccessibility() && child.getVisibility() != View.GONE) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + } } private void addInArray(View child, int index) { @@ -3804,6 +3861,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } onViewRemoved(view); + + if (view.isImportantForAccessibility() && view.getVisibility() != View.GONE) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + } } /** @@ -3812,13 +3873,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * the ViewGroup will be animated according to the animations defined in that LayoutTransition * object. By default, the transition object is null (so layout changes are not animated). * + *

      Replacing a non-null transition will cause that previous transition to be + * canceled, if it is currently running, to restore this container to + * its correct post-transition state.

      + * * @param transition The LayoutTransition object that will animated changes in layout. A value * of null means no transition will run on layout changes. * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges */ public void setLayoutTransition(LayoutTransition transition) { if (mTransition != null) { - mTransition.removeTransitionListener(mLayoutTransitionListener); + LayoutTransition previousTransition = mTransition; + previousTransition.cancel(); + previousTransition.removeTransitionListener(mLayoutTransitionListener); } mTransition = transition; if (mTransition != null) { @@ -4380,8 +4447,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Quick invalidation method that simply transforms the dirty rect into the parent's * coordinate system, pruning the invalidation if the parent has already been invalidated. + * + * @hide */ - private ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) { + protected ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) { if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { dirty.offset(left - mScrollX, top - mScrollY); @@ -4754,6 +4823,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled); } + private boolean hasBooleanFlag(int flag) { + return (mGroupFlags & flag) == flag; + } + private void setBooleanFlag(int flag, boolean value) { if (value) { mGroupFlags |= flag; @@ -4797,24 +4870,63 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES; } + private void setLayoutMode(int layoutMode, boolean explicitly) { + mLayoutMode = layoutMode; + setBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET, explicitly); + } + + /** + * Recursively traverse the view hierarchy, resetting the layoutMode of any + * descendants that had inherited a different layoutMode from a previous parent. + * Recursion terminates when a descendant's mode is: + *
        + *
      • Undefined
      • + *
      • The same as the root node's
      • + *
      • A mode that had been explicitly set
      • + *
          + * The first two clauses are optimizations. + * @param layoutModeOfRoot + */ + @Override + void invalidateInheritedLayoutMode(int layoutModeOfRoot) { + if (mLayoutMode == LAYOUT_MODE_UNDEFINED || + mLayoutMode == layoutModeOfRoot || + hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) { + return; + } + setLayoutMode(LAYOUT_MODE_UNDEFINED, false); + + // apply recursively + for (int i = 0, N = getChildCount(); i < N; i++) { + getChildAt(i).invalidateInheritedLayoutMode(layoutModeOfRoot); + } + } + /** - * Returns the basis of alignment during layout operations on this view group: + * Returns the basis of alignment during layout operations on this ViewGroup: * either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}. + *

          + * If no layoutMode was explicitly set, either programmatically or in an XML resource, + * the method returns the layoutMode of the view's parent ViewGroup if such a parent exists, + * otherwise the method returns a default value of {@link #LAYOUT_MODE_CLIP_BOUNDS}. * * @return the layout mode to use during layout operations * * @see #setLayoutMode(int) */ public int getLayoutMode() { + if (mLayoutMode == LAYOUT_MODE_UNDEFINED) { + int inheritedLayoutMode = (mParent instanceof ViewGroup) ? + ((ViewGroup) mParent).getLayoutMode() : LAYOUT_MODE_DEFAULT; + setLayoutMode(inheritedLayoutMode, false); + } return mLayoutMode; } /** - * Sets the basis of alignment during the layout of this view group. + * Sets the basis of alignment during the layout of this ViewGroup. * Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or * {@link #LAYOUT_MODE_OPTICAL_BOUNDS}. - *

          - * The default is {@link #LAYOUT_MODE_CLIP_BOUNDS}. * * @param layoutMode the layout mode to use during layout operations * @@ -4823,7 +4935,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ public void setLayoutMode(int layoutMode) { if (mLayoutMode != layoutMode) { - mLayoutMode = layoutMode; + invalidateInheritedLayoutMode(layoutMode); + setLayoutMode(layoutMode, layoutMode != LAYOUT_MODE_UNDEFINED); requestLayout(); } } @@ -5267,6 +5380,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** + * Returns whether layout calls on this container are currently being + * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}. + * + * @return true if layout calls are currently suppressed, false otherwise. + * + * @hide + */ + public boolean isLayoutSuppressed() { + return mSuppressLayout; + } + /** * {@inheritDoc} */ @@ -6343,7 +6468,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ private static final class TouchTarget { private static final int MAX_RECYCLED = 32; - private static final Object sRecycleLock = new Object(); + private static final Object sRecycleLock = new Object[0]; private static TouchTarget sRecycleBin; private static int sRecycledCount; @@ -6395,7 +6520,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /* Describes a hovered view. */ private static final class HoverTarget { private static final int MAX_RECYCLED = 32; - private static final Object sRecycleLock = new Object(); + private static final Object sRecycleLock = new Object[0]; private static HoverTarget sRecycleBin; private static int sRecycledCount; @@ -6614,8 +6739,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return sDebugPaint; } - private void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) { + private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) { if (sDebugLines== null) { + // TODO: This won't work with multiple UI threads in a single process sDebugLines = new float[16]; } diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java index 55109397f5959b84695e45a714527013c6621c46..975931ae859ca1ed678c2457812e098ea08088c2 100644 --- a/core/java/android/view/ViewOverlay.java +++ b/core/java/android/view/ViewOverlay.java @@ -15,6 +15,7 @@ */ package android.view; +import android.animation.LayoutTransition; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; @@ -169,6 +170,14 @@ public class ViewOverlay { child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]); } parent.removeView(child); + if (parent.getLayoutTransition() != null) { + // LayoutTransition will cause the child to delay removal - cancel it + parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING); + } + // fail-safe if view is still attached for any reason + if (child.getParent() != null) { + child.mParent = null; + } } super.addView(child); } @@ -291,6 +300,17 @@ public class ViewOverlay { } } + /** + * @hide + */ + @Override + protected ViewParent invalidateChildInParentFast(int left, int top, Rect dirty) { + if (mHostView instanceof ViewGroup) { + return ((ViewGroup) mHostView).invalidateChildInParentFast(left, top, dirty); + } + return null; + } + @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { if (mHostView != null) { diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index d79aa7efad4d936be277cadb4854d2d453b97f8f..013769349ca23fcc856f71b3efd8323e4631ab80 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -148,9 +148,11 @@ public interface ViewParent { /** * Change the z order of the child so it's on top of all other children. * This ordering change may affect layout, if this container - * uses an order-dependent layout scheme (e.g., LinearLayout). This + * uses an order-dependent layout scheme (e.g., LinearLayout). Prior + * to {@link android.os.Build.VERSION_CODES#KITKAT} this * method should be followed by calls to {@link #requestLayout()} and - * {@link View#invalidate()} on this parent. + * {@link View#invalidate()} on this parent to force the parent to redraw + * with the new child ordering. * * @param child The child to bring to the top of the z order */ @@ -269,10 +271,25 @@ public interface ViewParent { /** * Called when a child view now has or no longer is tracking transient state. * + *

          "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.

          + * + *

          Transient state is useful for hinting to other components of the View system + * that a particular view is tracking something complex but encapsulated. + * A ListView 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.

          + * + *

          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.

          + * * @param child Child view whose state has changed * @param hasTransientState true if this child has transient state - * - * @hide */ public void childHasTransientStateChanged(View child, boolean hasTransientState); @@ -292,21 +309,27 @@ public interface ViewParent { public ViewParent getParentForAccessibility(); /** - * A child notifies its parent that its state for accessibility has changed. - * That is some of the child properties reported to accessibility services has - * changed, hence the interested services have to be notified for the new state. - * - * @hide + * Notifies a view parent that the accessibility state of one of its + * descendants has changed and that the structure of the subtree is + * different. + * @param child The direct child whose subtree has changed. + * @param source The descendant view that changed. + * @param changeType A bit mask of the types of changes that occurred. One + * or more of: + *
            + *
          • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION} + *
          • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE} + *
          • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT} + *
          • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED} + *
          */ - public void childAccessibilityStateChanged(View child); + public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType); /** * Tells if this view parent can resolve the layout direction. * See {@link View#setLayoutDirection(int)} * * @return True if this view parent can resolve the layout direction. - * - * @hide */ public boolean canResolveLayoutDirection(); @@ -315,8 +338,6 @@ public interface ViewParent { * See {@link View#setLayoutDirection(int)} * * @return True if this view parent layout direction is resolved. - * - * @hide */ public boolean isLayoutDirectionResolved(); @@ -325,8 +346,6 @@ public interface ViewParent { * * @return {@link View#LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns * {@link View#LAYOUT_DIRECTION_LTR} if the layout direction is not RTL. - * - * @hide */ public int getLayoutDirection(); @@ -335,8 +354,6 @@ public interface ViewParent { * See {@link View#setTextDirection(int)} * * @return True if this view parent can resolve the text direction. - * - * @hide */ public boolean canResolveTextDirection(); @@ -345,8 +362,6 @@ public interface ViewParent { * See {@link View#setTextDirection(int)} * * @return True if this view parent text direction is resolved. - * - * @hide */ public boolean isTextDirectionResolved(); @@ -360,8 +375,6 @@ public interface ViewParent { * {@link View#TEXT_DIRECTION_LTR}, * {@link View#TEXT_DIRECTION_RTL}, * {@link View#TEXT_DIRECTION_LOCALE} - * - * @hide */ public int getTextDirection(); @@ -370,8 +383,6 @@ public interface ViewParent { * See {@link View#setTextAlignment(int)} * * @return True if this view parent can resolve the text alignment. - * - * @hide */ public boolean canResolveTextAlignment(); @@ -380,8 +391,6 @@ public interface ViewParent { * See {@link View#setTextAlignment(int)} * * @return True if this view parent text alignment is resolved. - * - * @hide */ public boolean isTextAlignmentResolved(); @@ -396,8 +405,6 @@ public interface ViewParent { * {@link View#TEXT_ALIGNMENT_TEXT_END}, * {@link View#TEXT_ALIGNMENT_VIEW_START}, * {@link View#TEXT_ALIGNMENT_VIEW_END} - * - * @hide */ public int getTextAlignment(); } diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index e6bf420acccc7c47872ab95b5cac9e52e76ff743..67a94be5959107434d3de9b506afe01d836165d3 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -93,10 +93,15 @@ public class ViewPropertyAnimator { private boolean mInterpolatorSet = false; /** - * Listener for the lifecycle events of the underlying + * Listener for the lifecycle events of the underlying ValueAnimator object. */ private Animator.AnimatorListener mListener = null; + /** + * Listener for the update events of the underlying ValueAnimator object. + */ + private ValueAnimator.AnimatorUpdateListener mUpdateListener = null; + /** * A lazily-created ValueAnimator used in order to get some default animator properties * (duration, start delay, interpolator, etc.). @@ -353,7 +358,10 @@ public class ViewPropertyAnimator { * Sets a listener for events in the underlying Animators that run the property * animations. * - * @param listener The listener to be called with AnimatorListener events. + * @see Animator.AnimatorListener + * + * @param listener The listener to be called with AnimatorListener events. A value of + * null removes any existing listener. * @return This object, allowing calls to methods in this class to be chained. */ public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) { @@ -361,6 +369,25 @@ public class ViewPropertyAnimator { return this; } + /** + * Sets a listener for update events in the underlying ValueAnimator that runs + * the property animations. Note that the underlying animator is animating between + * 0 and 1 (these values are then turned into the actual property values internally + * by ViewPropertyAnimator). So the animator cannot give information on the current + * values of the properties being animated by this ViewPropertyAnimator, although + * the view object itself can be queried to get the current values. + * + * @see android.animation.ValueAnimator.AnimatorUpdateListener + * + * @param listener The listener to be called with update events. A value of + * null removes any existing listener. + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator setUpdateListener(ValueAnimator.AnimatorUpdateListener listener) { + mUpdateListener = listener; + return this; + } + /** * Starts the currently pending property animations immediately. Calling start() * is optional because all animations start automatically at the next opportunity. However, @@ -675,6 +702,9 @@ public class ViewPropertyAnimator { @Override public void run() { mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + if (mView.isAttachedToWindow()) { + mView.buildLayer(); + } } }; final int currentLayerType = mView.getLayerType(); @@ -1073,6 +1103,9 @@ public class ViewPropertyAnimator { } else { mView.invalidateViewProperty(false, false); } + if (mUpdateListener != null) { + mUpdateListener.onAnimationUpdate(animation); + } } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 6b2ed912d9d41a82b20f4dc237b3a4817effe21b..946106868d57cfa88ce80758c1021270bbf2e221 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -23,7 +23,6 @@ import android.content.ClipDescription; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -69,6 +68,7 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.Surface.OutOfResourcesException; import android.widget.Scroller; import com.android.internal.R; @@ -108,13 +108,12 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_FPS = false; private static final boolean DEBUG_INPUT_PROCESSING = false || LOCAL_LOGV; - private static final boolean USE_RENDER_THREAD = false; - /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. */ - private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering"; + private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering"; + private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media"; /** * Maximum time we allow the user to roll the trackball enough to generate @@ -126,14 +125,10 @@ public final class ViewRootImpl implements ViewParent, static final ArrayList sFirstDrawHandlers = new ArrayList(); static boolean sFirstDrawComplete = false; - + static final ArrayList sConfigCallbacks = new ArrayList(); - private static boolean sUseRenderThread = false; - private static boolean sRenderThreadQueried = false; - private static final Object[] sRenderThreadQueryLock = new Object[0]; - final Context mContext; final IWindowSession mWindowSession; final Display mDisplay; @@ -167,14 +162,14 @@ public final class ViewRootImpl implements ViewParent, // Set to true if the owner of this window is in the stopped state, // so the window should no longer be active. boolean mStopped = false; - + boolean mLastInCompatMode = false; SurfaceHolder.Callback2 mSurfaceHolderCallback; BaseSurfaceHolder mSurfaceHolder; boolean mIsCreating; boolean mDrawingAllowed; - + final Region mTransparentRegion; final Region mPreviousTransparentRegion; @@ -192,7 +187,7 @@ public final class ViewRootImpl implements ViewParent, InputQueue mInputQueue; FallbackEventHandler mFallbackEventHandler; Choreographer mChoreographer; - + final Rect mTempRect; // used in the transaction to not thrash the heap. final Rect mVisRect; // used to retrieve visible rect of focused view. @@ -235,6 +230,8 @@ public final class ViewRootImpl implements ViewParent, InputStage mFirstInputStage; InputStage mFirstPostImeInputStage; + boolean mFlipControllerFallbackKeys; + boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -245,7 +242,7 @@ public final class ViewRootImpl implements ViewParent, boolean mAdded; boolean mAddedTouchMode; - final CompatibilityInfoHolder mCompatibilityInfo; + final DisplayAdjustments mDisplayAdjustments; // These are accessed by multiple threads. final Rect mWinFrame; // frame given by window manager. @@ -281,18 +278,20 @@ public final class ViewRootImpl implements ViewParent, volatile Object mLocalDragState; final PointF mDragPoint = new PointF(); final PointF mLastTouchPoint = new PointF(); - - private boolean mProfileRendering; + + private boolean mProfileRendering; private Choreographer.FrameCallback mRenderProfiler; private boolean mRenderProfilingEnabled; + private boolean mMediaDisabled; + // Variables to track frames per second, enabled via DEBUG_FPS flag private long mFpsStartTime = -1; private long mFpsPrevTime = -1; private int mFpsNumFrames; private final ArrayList mDisplayLists = new ArrayList(); - + /** * see {@link #playSoundEffect(int)} */ @@ -317,6 +316,9 @@ public final class ViewRootImpl implements ViewParent, private int mViewLayoutDirectionInitial; + /** Set to true once doDie() has been called. */ + private boolean mRemoved; + /** * Consistency verifier for debugging purposes. */ @@ -330,15 +332,14 @@ public final class ViewRootImpl implements ViewParent, int localValue; int localChanges; } - + public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); mDisplay = display; mBasePackageName = context.getBasePackageName(); - CompatibilityInfoHolder cih = display.getCompatibilityInfo(); - mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder(); + mDisplayAdjustments = display.getDisplayAdjustments(); mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); @@ -367,41 +368,14 @@ public final class ViewRootImpl implements ViewParent, mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context); mChoreographer = Choreographer.getInstance(); + mFlipControllerFallbackKeys = + context.getResources().getBoolean(R.bool.flip_controller_fallback_keys); PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mAttachInfo.mScreenOn = powerManager.isScreenOn(); loadSystemProperties(); } - /** - * @return True if the application requests the use of a separate render thread, - * false otherwise - */ - private static boolean isRenderThreadRequested(Context context) { - if (USE_RENDER_THREAD) { - synchronized (sRenderThreadQueryLock) { - if (!sRenderThreadQueried) { - final PackageManager packageManager = context.getPackageManager(); - final String packageName = context.getApplicationInfo().packageName; - try { - ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, - PackageManager.GET_META_DATA); - if (applicationInfo.metaData != null) { - sUseRenderThread = applicationInfo.metaData.getBoolean( - "android.graphics.renderThread", false); - } - } catch (PackageManager.NameNotFoundException e) { - } finally { - sRenderThreadQueried = true; - } - } - return sUseRenderThread; - } - } else { - return false; - } - } - public static void addFirstDrawHandler(Runnable callback) { synchronized (sFirstDrawHandlers) { if (!sFirstDrawComplete) { @@ -409,13 +383,13 @@ public final class ViewRootImpl implements ViewParent, } } } - + public static void addConfigCallback(ComponentCallbacks callback) { synchronized (sConfigCallbacks) { sConfigCallbacks.add(callback); } } - + // FIXME for perf testing only private boolean mProfile = false; @@ -474,12 +448,13 @@ public final class ViewRootImpl implements ViewParent, } } - CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get(); + CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(); + mDisplayAdjustments.setActivityToken(attrs.token); // If the application owns the surface, don't enable hardware acceleration if (mSurfaceHolder == null) { - enableHardwareAcceleration(mView.getContext(), attrs); + enableHardwareAcceleration(attrs); } boolean restore = false; @@ -492,7 +467,7 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs); if (!compatibilityInfo.supportsScreen()) { - attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = true; } @@ -539,7 +514,7 @@ public final class ViewRootImpl implements ViewParent, attrs.restore(); } } - + if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } @@ -619,8 +594,8 @@ public final class ViewRootImpl implements ViewParent, // Set up the input pipeline. CharSequence counterSuffix = attrs.getTitle(); - InputStage syntheticStage = new SyntheticInputStage(); - InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticStage); + InputStage syntheticInputStage = new SyntheticInputStage(); + InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); @@ -637,16 +612,13 @@ public final class ViewRootImpl implements ViewParent, } } - void destroyHardwareResources() { - if (mAttachInfo.mHardwareRenderer != null) { - if (mAttachInfo.mHardwareRenderer.isEnabled()) { - mAttachInfo.mHardwareRenderer.destroyLayers(mView); - } - mAttachInfo.mHardwareRenderer.destroy(false); - } + /** Whether the window is in local focus mode or not */ + private boolean isInLocalFocusMode() { + return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; } - void terminateHardwareResources() { + void destroyHardwareResources() { + invalidateDisplayLists(); if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView); mAttachInfo.mHardwareRenderer.destroy(false); @@ -660,6 +632,7 @@ public final class ViewRootImpl implements ViewParent, HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE); } } else { + invalidateDisplayLists(); if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { mAttachInfo.mHardwareRenderer.destroyLayers(mView); @@ -673,6 +646,18 @@ public final class ViewRootImpl implements ViewParent, } } + void flushHardwareLayerUpdates() { + if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled() && + mAttachInfo.mHardwareRenderer.validate()) { + mAttachInfo.mHardwareRenderer.flushLayerUpdates(); + } + } + + void dispatchFlushHardwareLayerUpdates() { + mHandler.removeMessages(MSG_FLUSH_LAYER_UPDATES); + mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_FLUSH_LAYER_UPDATES)); + } + public boolean attachFunctor(int functor) { //noinspection SimplifiableIfStatement if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { @@ -687,7 +672,7 @@ public final class ViewRootImpl implements ViewParent, } } - private void enableHardwareAcceleration(Context context, WindowManager.LayoutParams attrs) { + private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) { mAttachInfo.mHardwareAccelerated = false; mAttachInfo.mHardwareAccelerationRequested = false; @@ -695,7 +680,7 @@ public final class ViewRootImpl implements ViewParent, if (mTranslator != null) return; // Try to enable hardware acceleration if requested - final boolean hardwareAccelerated = + final boolean hardwareAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; if (hardwareAccelerated) { @@ -722,16 +707,11 @@ public final class ViewRootImpl implements ViewParent, // Don't enable hardware acceleration when we're not on the main thread if (!HardwareRenderer.sSystemRendererDisabled && Looper.getMainLooper() != Looper.myLooper()) { - Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " + Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " + "acceleration outside of the main thread, aborting"); return; } - final boolean renderThread = isRenderThreadRequested(context); - if (renderThread) { - Log.i(HardwareRenderer.LOG_TAG, "Render threat initiated"); - } - if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(true); } @@ -768,16 +748,21 @@ public final class ViewRootImpl implements ViewParent, // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; // preserve compatible window flag if exists. - int compatibleWindowFlag = - mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + int compatibleWindowFlag = mWindowAttributes.privateFlags + & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; // transfer over system UI visibility values as they carry current state. attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility; attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility; mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs); + if ((mWindowAttributesChangesFlag + & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) { + // Recompute system ui visibility. + mAttachInfo.mRecomputeGlobalAttributes = true; + } if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } - mWindowAttributes.flags |= compatibleWindowFlag; + mWindowAttributes.privateFlags |= compatibleWindowFlag; applyKeepScreenOnFlag(mWindowAttributes); @@ -865,6 +850,7 @@ public final class ViewRootImpl implements ViewParent, invalidateChildInParent(null, dirty); } + @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); @@ -922,10 +908,12 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public ViewParent getParent() { return null; } + @Override public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { if (child != mView) { throw new RuntimeException("child is not mine, honest!"); @@ -935,6 +923,7 @@ public final class ViewRootImpl implements ViewParent, return r.intersect(0, 0, mWidth, mHeight); } + @Override public void bringChildToFront(View child) { } @@ -943,9 +932,14 @@ public final class ViewRootImpl implements ViewParent, } void disposeResizeBuffer() { - if (mResizeBuffer != null) { - mResizeBuffer.destroy(); - mResizeBuffer = null; + if (mResizeBuffer != null && mAttachInfo.mHardwareRenderer != null) { + mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() { + @Override + public void run() { + mResizeBuffer.destroy(); + mResizeBuffer = null; + } + }); } } @@ -1037,6 +1031,7 @@ public final class ViewRootImpl implements ViewParent, mView.dispatchCollectViewAttributes(attachInfo, 0); attachInfo.mSystemUiVisibility &= ~attachInfo.mDisabledSystemUiVisibility; WindowManager.LayoutParams params = mWindowAttributes; + attachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params); if (attachInfo.mKeepScreenOn != oldScreenOn || attachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility || attachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) { @@ -1050,6 +1045,18 @@ public final class ViewRootImpl implements ViewParent, return false; } + private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) { + int vis = 0; + // Translucent decor window flags imply stable system ui visibility. + if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) { + vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + } + if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0) { + vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + } + return vis; + } + private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { int childWidthMeasureSpec; @@ -1151,28 +1158,29 @@ public final class ViewRootImpl implements ViewParent, surfaceChanged = true; params = lp; } - CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get(); + CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { params = lp; mFullRedrawNeeded = true; mLayoutRequested = true; if (mLastInCompatMode) { - params.flags &= ~WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = false; } else { - params.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = true; } } - + mWindowAttributesChangesFlag = 0; - + Rect frame = mWinFrame; if (mFirst) { mFullRedrawNeeded = true; mLayoutRequested = true; - if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) { + if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL + || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); @@ -1266,7 +1274,8 @@ public final class ViewRootImpl implements ViewParent, || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowSizeMayChange = true; - if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) { + if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL + || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); @@ -1358,8 +1367,12 @@ public final class ViewRootImpl implements ViewParent, || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT && frame.height() < desiredWindowHeight && frame.height() != mHeight)); + // Determine whether to compute insets. + // If there are no inset listeners remaining then we may still need to compute + // insets in case the old insets were non-empty and must be reset. final boolean computesInternalInsets = - attachInfo.mTreeObserver.hasComputeInternalInsetsListeners(); + attachInfo.mTreeObserver.hasComputeInternalInsetsListeners() + || attachInfo.mHasNonEmptyGivenInternalInsets; boolean insetsPending = false; int relayoutResult = 0; @@ -1397,9 +1410,9 @@ public final class ViewRootImpl implements ViewParent, final int surfaceGenerationId = mSurface.getGenerationId(); relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); - if (!mDrawDuringWindowsAnimating) { - mWindowsAnimating |= - (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0; + if (!mDrawDuringWindowsAnimating && + (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) { + mWindowsAnimating = true; } if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() @@ -1465,7 +1478,7 @@ public final class ViewRootImpl implements ViewParent, } DisplayList displayList = mView.mDisplayList; - if (displayList != null) { + if (displayList != null && displayList.isValid()) { layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN); } else { @@ -1486,8 +1499,7 @@ public final class ViewRootImpl implements ViewParent, if (mResizeBuffer != null) { mResizeBuffer.end(hwRendererCanvas); if (!completed) { - mResizeBuffer.destroy(); - mResizeBuffer = null; + disposeResizeBuffer(); } } } @@ -1535,7 +1547,7 @@ public final class ViewRootImpl implements ViewParent, try { hwInitialized = mAttachInfo.mHardwareRenderer.initialize( mHolder.getSurface()); - } catch (Surface.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } @@ -1562,7 +1574,7 @@ public final class ViewRootImpl implements ViewParent, mFullRedrawNeeded = true; try { mAttachInfo.mHardwareRenderer.updateSurface(mHolder.getSurface()); - } catch (Surface.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } @@ -1657,23 +1669,23 @@ public final class ViewRootImpl implements ViewParent, || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); - + if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" + mWidth + " measuredWidth=" + host.getMeasuredWidth() + " mHeight=" + mHeight + " measuredHeight=" + host.getMeasuredHeight() + " coveredInsetsChanged=" + contentInsetsChanged); - + // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); - + // Implementation of weights from WindowManager.LayoutParams // We just grow the dimensions as needed and re-measure if // needs be int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain = false; - + if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, @@ -1686,14 +1698,14 @@ public final class ViewRootImpl implements ViewParent, MeasureSpec.EXACTLY); measureAgain = true; } - + if (measureAgain) { if (DEBUG_LAYOUT) Log.v(TAG, "And hey let's measure once more: width=" + width + " height=" + height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } - + layoutRequested = true; } } @@ -1766,10 +1778,6 @@ public final class ViewRootImpl implements ViewParent, if (triggerGlobalLayoutListener) { attachInfo.mRecomputeGlobalAttributes = false; attachInfo.mTreeObserver.dispatchOnGlobalLayout(); - - if (AccessibilityManager.getInstance(host.mContext).isEnabled()) { - postSendWindowContentChangedCallback(mView); - } } if (computesInternalInsets) { @@ -1779,6 +1787,7 @@ public final class ViewRootImpl implements ViewParent, // Compute new insets in place. attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); + attachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty(); // Tell the window manager. if (insetsPending || !mLastGivenInsets.equals(insets)) { @@ -1837,7 +1846,7 @@ public final class ViewRootImpl implements ViewParent, mNewSurfaceNeeded = false; mViewVisibility = viewVisibility; - if (mAttachInfo.mHasWindowFocus) { + if (mAttachInfo.mHasWindowFocus && !isInLocalFocusMode()) { final boolean imTarget = WindowManager.LayoutParams .mayUseInputMethod(mWindowAttributes.flags); if (imTarget != mLastWasImTarget) { @@ -1868,7 +1877,7 @@ public final class ViewRootImpl implements ViewParent, } mPendingTransitions.clear(); } - + performDraw(); } } else { @@ -2095,6 +2104,7 @@ public final class ViewRootImpl implements ViewParent, return validLayoutRequesters; } + @Override public void requestTransparentRegion(View child) { // the test below should not fail unless someone is messing with us checkThread(); @@ -2145,10 +2155,12 @@ public final class ViewRootImpl implements ViewParent, int mResizeAlpha; final Paint mResizePaint = new Paint(); + @Override public void onHardwarePreDraw(HardwareCanvas canvas) { canvas.translate(0, -mHardwareYOffset); } + @Override public void onHardwarePostDraw(HardwareCanvas canvas) { if (mResizeBuffer != null) { mResizePaint.setAlpha(mResizeAlpha); @@ -2382,7 +2394,7 @@ public final class ViewRootImpl implements ViewParent, try { attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, mHolder.getSurface()); - } catch (Surface.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } @@ -2568,7 +2580,7 @@ public final class ViewRootImpl implements ViewParent, for (int i = 0; i < count; i++) { final DisplayList displayList = displayLists.get(i); if (displayList.isDirty()) { - displayList.clear(); + displayList.reset(); } } @@ -2758,6 +2770,7 @@ public final class ViewRootImpl implements ViewParent, mAccessibilityFocusedVirtualView = node; } + @Override public void requestChildFocus(View child, View focused) { if (DEBUG_INPUT_RESIZE) { Log.v(TAG, "Request child focus: focus now " + focused); @@ -2766,6 +2779,7 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } + @Override public void clearChildFocus(View child) { if (DEBUG_INPUT_RESIZE) { Log.v(TAG, "Clearing child focus"); @@ -2779,6 +2793,7 @@ public final class ViewRootImpl implements ViewParent, return null; } + @Override public void focusableViewAvailable(View v) { checkThread(); if (mView != null) { @@ -2800,6 +2815,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void recomputeViewAttributes(View child) { checkThread(); if (mView == child) { @@ -2867,8 +2883,8 @@ public final class ViewRootImpl implements ViewParent, + mWindowAttributes.getTitle() + ": " + config); - CompatibilityInfo ci = mCompatibilityInfo.getIfNeeded(); - if (ci != null) { + CompatibilityInfo ci = mDisplayAdjustments.getCompatibilityInfo(); + if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) { config = new Configuration(config); ci.applyToConfiguration(mNoncompatDensity, config); } @@ -2894,8 +2910,11 @@ public final class ViewRootImpl implements ViewParent, mView.dispatchConfigurationChanged(config); } } + + mFlipControllerFallbackKeys = + mContext.getResources().getBoolean(R.bool.flip_controller_fallback_keys); } - + /** * Return true if child is an ancestor of parent, (or equal to the parent). */ @@ -2925,7 +2944,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_RESIZED = 4; private final static int MSG_RESIZED_REPORT = 5; private final static int MSG_WINDOW_FOCUS_CHANGED = 6; - private final static int MSG_DISPATCH_KEY = 7; + private final static int MSG_DISPATCH_INPUT_EVENT = 7; private final static int MSG_DISPATCH_APP_VISIBILITY = 8; private final static int MSG_DISPATCH_GET_NEW_SURFACE = 9; private final static int MSG_DISPATCH_KEY_FROM_IME = 11; @@ -2942,6 +2961,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_DISPATCH_DONE_ANIMATING = 22; private final static int MSG_INVALIDATE_WORLD = 23; private final static int MSG_WINDOW_MOVED = 24; + private final static int MSG_FLUSH_LAYER_UPDATES = 25; final class ViewRootHandler extends Handler { @Override @@ -2959,8 +2979,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_RESIZED_REPORT"; case MSG_WINDOW_FOCUS_CHANGED: return "MSG_WINDOW_FOCUS_CHANGED"; - case MSG_DISPATCH_KEY: - return "MSG_DISPATCH_KEY"; + case MSG_DISPATCH_INPUT_EVENT: + return "MSG_DISPATCH_INPUT_EVENT"; case MSG_DISPATCH_APP_VISIBILITY: return "MSG_DISPATCH_APP_VISIBILITY"; case MSG_DISPATCH_GET_NEW_SURFACE: @@ -2991,6 +3011,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_DISPATCH_DONE_ANIMATING"; case MSG_WINDOW_MOVED: return "MSG_WINDOW_MOVED"; + case MSG_FLUSH_LAYER_UPDATES: + return "MSG_FLUSH_LAYER_UPDATES"; } return super.getMessageName(message); } @@ -3087,7 +3109,7 @@ public final class ViewRootImpl implements ViewParent, try { mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mHolder.getSurface()); - } catch (Surface.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException locking surface", e); try { if (!mWindowSession.outOfMemory(mWindow)) { @@ -3108,7 +3130,8 @@ public final class ViewRootImpl implements ViewParent, InputMethodManager imm = InputMethodManager.peekInstance(); if (mView != null) { - if (hasWindowFocus && imm != null && mLastWasImTarget) { + if (hasWindowFocus && imm != null && mLastWasImTarget && + !isInLocalFocusMode()) { imm.startGettingWindowFocus(mView); } mAttachInfo.mKeyDispatchState.reset(); @@ -3119,7 +3142,7 @@ public final class ViewRootImpl implements ViewParent, // Note: must be done after the focus change callbacks, // so all of the view state is set up correctly. if (hasWindowFocus) { - if (imm != null && mLastWasImTarget) { + if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { imm.onWindowFocus(mView, mView.findFocus(), mWindowAttributes.softInputMode, !mHasHadWindowFocus, mWindowAttributes.flags); @@ -3147,8 +3170,8 @@ public final class ViewRootImpl implements ViewParent, case MSG_DIE: doDie(); break; - case MSG_DISPATCH_KEY: { - KeyEvent event = (KeyEvent)msg.obj; + case MSG_DISPATCH_INPUT_EVENT: { + InputEvent event = (InputEvent)msg.obj; enqueueInputEvent(event, null, 0, true); } break; case MSG_DISPATCH_KEY_FROM_IME: { @@ -3213,6 +3236,9 @@ public final class ViewRootImpl implements ViewParent, invalidateWorld(mView); } } break; + case MSG_FLUSH_LAYER_UPDATES: { + flushHardwareLayerUpdates(); + } break; } } } @@ -3235,7 +3261,9 @@ public final class ViewRootImpl implements ViewParent, // tell the window manager try { - mWindowSession.setInTouchMode(inTouchMode); + if (!isInLocalFocusMode()) { + mWindowSession.setInTouchMode(inTouchMode); + } } catch (RemoteException e) { throw new RuntimeException(e); } @@ -3263,24 +3291,23 @@ public final class ViewRootImpl implements ViewParent, } private boolean enterTouchMode() { - if (mView != null) { - if (mView.hasFocus()) { - // note: not relying on mFocusedView here because this could - // be when the window is first being added, and mFocused isn't - // set yet. - final View focused = mView.findFocus(); - if (focused != null && !focused.isFocusableInTouchMode()) { - final ViewGroup ancestorToTakeFocus = - findAncestorToTakeFocusInTouchMode(focused); - if (ancestorToTakeFocus != null) { - // there is an ancestor that wants focus after its descendants that - // is focusable in touch mode.. give it focus - return ancestorToTakeFocus.requestFocus(); - } else { - // nothing appropriate to have focus in touch mode, clear it out - focused.unFocus(); - return true; - } + if (mView != null && mView.hasFocus()) { + // note: not relying on mFocusedView here because this could + // be when the window is first being added, and mFocused isn't + // set yet. + final View focused = mView.findFocus(); + if (focused != null && !focused.isFocusableInTouchMode()) { + final ViewGroup ancestorToTakeFocus = findAncestorToTakeFocusInTouchMode(focused); + if (ancestorToTakeFocus != null) { + // there is an ancestor that wants focus after its + // descendants that is focusable in touch mode.. give it + // focus + return ancestorToTakeFocus.requestFocus(); + } else { + // There's nothing to focus. Clear and propagate through the + // hierarchy, but don't attempt to place new focus. + focused.clearFocusInternal(true, false); + return true; } } } @@ -3638,7 +3665,7 @@ public final class ViewRootImpl implements ViewParent, @Override protected int onProcess(QueuedInputEvent q) { - if (mLastWasImTarget) { + if (mLastWasImTarget && !isInLocalFocusMode()) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { final InputEvent event = q.mEvent; @@ -3773,6 +3800,9 @@ public final class ViewRootImpl implements ViewParent, if (q.mEvent instanceof KeyEvent) { return processKeyEvent(q); } else { + // If delivering a new non-key event, make sure the window is + // now allowed to start updating. + handleDispatchDoneAnimating(); final int source = q.mEvent.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { return processPointerEvent(q); @@ -3787,6 +3817,12 @@ public final class ViewRootImpl implements ViewParent, private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; + if (event.getAction() != KeyEvent.ACTION_UP) { + // If delivering a new key event, make sure the window is + // now allowed to start updating. + handleDispatchDoneAnimating(); + } + // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { return FINISH_HANDLED; @@ -3914,6 +3950,7 @@ public final class ViewRootImpl implements ViewParent, private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler(); private final SyntheticTouchNavigationHandler mTouchNavigation = new SyntheticTouchNavigationHandler(); + private final SyntheticKeyHandler mKeys = new SyntheticKeyHandler(); public SyntheticInputStage() { super(null); @@ -3936,7 +3973,12 @@ public final class ViewRootImpl implements ViewParent, mTouchNavigation.process(event); return FINISH_HANDLED; } + } else if (q.mEvent instanceof KeyEvent) { + if (mKeys.process((KeyEvent) q.mEvent)) { + return FINISH_HANDLED; + } } + return FORWARD; } @@ -4386,12 +4428,6 @@ public final class ViewRootImpl implements ViewParent, /* TODO: These constants should eventually be moved to ViewConfiguration. */ - // Tap timeout in milliseconds. - private static final int TAP_TIMEOUT = 250; - - // The maximum distance traveled for a gesture to be considered a tap in millimeters. - private static final int TAP_SLOP_MILLIMETERS = 5; - // The nominal distance traveled to move by one unit. private static final int TICK_DISTANCE_MILLIMETERS = 12; @@ -4419,10 +4455,6 @@ public final class ViewRootImpl implements ViewParent, /* Configuration for the current input device. */ - // The tap timeout and scaled slop. - private int mConfigTapTimeout; - private float mConfigTapSlop; - // The scaled tick distance. A movement of this amount should generally translate // into a single dpad event in a given direction. private float mConfigTickDistance; @@ -4468,6 +4500,9 @@ public final class ViewRootImpl implements ViewParent, private boolean mFlinging; private float mFlingVelocity; + // The last time a confirm key was pressed on the touch nav device + private long mLastConfirmKeyTime = Long.MAX_VALUE; + public SyntheticTouchNavigationHandler() { super(true); } @@ -4504,8 +4539,6 @@ public final class ViewRootImpl implements ViewParent, float nominalRes = (xRes + yRes) * 0.5f; // Precompute all of the configuration thresholds we will need. - mConfigTapTimeout = TAP_TIMEOUT; - mConfigTapSlop = TAP_SLOP_MILLIMETERS * nominalRes; mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes; mConfigMinFlingVelocity = MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance; @@ -4515,8 +4548,6 @@ public final class ViewRootImpl implements ViewParent, if (LOCAL_DEBUG) { Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId + " (" + Integer.toHexString(mCurrentSource) + "): " - + "mConfigTapTimeout=" + mConfigTapTimeout - + ", mConfigTapSlop=" + mConfigTapSlop + ", mConfigTickDistance=" + mConfigTickDistance + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity); @@ -4578,15 +4609,7 @@ public final class ViewRootImpl implements ViewParent, // Detect taps and flings. if (action == MotionEvent.ACTION_UP) { - if (!mConsumedMovement - && Math.hypot(mLastX - mStartX, mLastY - mStartY) < mConfigTapSlop - && time <= mStartTime + mConfigTapTimeout) { - // It's a tap! - finishKeys(time); - sendKeyDownOrRepeat(time, KeyEvent.KEYCODE_DPAD_CENTER, metaState); - sendKeyUp(time); - } else if (mConsumedMovement - && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) { + if (mConsumedMovement && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) { // It might be a fling. mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity); final float vx = mVelocityTracker.getXVelocity(mActivePointerId); @@ -4785,6 +4808,63 @@ public final class ViewRootImpl implements ViewParent, }; } + final class SyntheticKeyHandler { + + public boolean process(KeyEvent event) { + // In some locales (like Japan) controllers use B for confirm and A for back, rather + // than vice versa, so we need to special case this here since the input system itself + // is not locale-aware. + int keyCode; + switch(event.getKeyCode()) { + case KeyEvent.KEYCODE_BUTTON_A: + case KeyEvent.KEYCODE_BUTTON_C: + case KeyEvent.KEYCODE_BUTTON_X: + case KeyEvent.KEYCODE_BUTTON_Z: + keyCode = mFlipControllerFallbackKeys ? + KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_DPAD_CENTER; + break; + case KeyEvent.KEYCODE_BUTTON_B: + case KeyEvent.KEYCODE_BUTTON_Y: + keyCode = mFlipControllerFallbackKeys ? + KeyEvent.KEYCODE_DPAD_CENTER : KeyEvent.KEYCODE_BACK; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + case KeyEvent.KEYCODE_BUTTON_THUMBR: + case KeyEvent.KEYCODE_BUTTON_START: + case KeyEvent.KEYCODE_BUTTON_1: + case KeyEvent.KEYCODE_BUTTON_2: + case KeyEvent.KEYCODE_BUTTON_3: + case KeyEvent.KEYCODE_BUTTON_4: + case KeyEvent.KEYCODE_BUTTON_5: + case KeyEvent.KEYCODE_BUTTON_6: + case KeyEvent.KEYCODE_BUTTON_7: + case KeyEvent.KEYCODE_BUTTON_8: + case KeyEvent.KEYCODE_BUTTON_9: + case KeyEvent.KEYCODE_BUTTON_10: + case KeyEvent.KEYCODE_BUTTON_11: + case KeyEvent.KEYCODE_BUTTON_12: + case KeyEvent.KEYCODE_BUTTON_13: + case KeyEvent.KEYCODE_BUTTON_14: + case KeyEvent.KEYCODE_BUTTON_15: + case KeyEvent.KEYCODE_BUTTON_16: + keyCode = KeyEvent.KEYCODE_DPAD_CENTER; + break; + case KeyEvent.KEYCODE_BUTTON_SELECT: + case KeyEvent.KEYCODE_BUTTON_MODE: + keyCode = KeyEvent.KEYCODE_MENU; + default: + return false; + } + + enqueueInputEvent(new KeyEvent(event.getDownTime(), event.getEventTime(), + event.getAction(), keyCode, event.getRepeatCount(), event.getMetaState(), + event.getDeviceId(), event.getScanCode(), + event.getFlags() | KeyEvent.FLAG_FALLBACK, event.getSource())); + return true; + } + + } + /** * Returns true if the key is used for keyboard navigation. * @param keyEvent The key event. @@ -4952,7 +5032,7 @@ public final class ViewRootImpl implements ViewParent, // most recent data. mSeq = args.seq; mAttachInfo.mForceReportNewAttributes = true; - scheduleTraversals(); + scheduleTraversals(); } if (mView == null) return; if (args.localChanges != 0) { @@ -5042,7 +5122,7 @@ public final class ViewRootImpl implements ViewParent, if (restore) { params.restore(); } - + if (mTranslator != null) { mTranslator.translateRectInScreenToAppWinFrame(mWinFrame); mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets); @@ -5055,9 +5135,14 @@ public final class ViewRootImpl implements ViewParent, /** * {@inheritDoc} */ + @Override public void playSoundEffect(int effectId) { checkThread(); + if (mMediaDisabled) { + return; + } + try { final AudioManager audioManager = getAudioManager(); @@ -5091,6 +5176,7 @@ public final class ViewRootImpl implements ViewParent, /** * {@inheritDoc} */ + @Override public boolean performHapticFeedback(int effectId, boolean always) { try { return mWindowSession.performHapticFeedback(mWindow, effectId, always); @@ -5102,6 +5188,7 @@ public final class ViewRootImpl implements ViewParent, /** * {@inheritDoc} */ + @Override public View focusSearch(View focused, int direction) { checkThread(); if (!(mView instanceof ViewGroup)) { @@ -5113,7 +5200,7 @@ public final class ViewRootImpl implements ViewParent, public void debug() { mView.debug(); } - + public void dumpGfxInfo(int[] info) { info[0] = info[1] = 0; if (mView != null) { @@ -5138,26 +5225,36 @@ public final class ViewRootImpl implements ViewParent, } } - public void die(boolean immediate) { + /** + * @param immediate True, do now if not in traversal. False, put on queue and do later. + * @return True, request has been queued. False, request has been completed. + */ + boolean die(boolean immediate) { // Make sure we do execute immediately if we are in the middle of a traversal or the damage // done by dispatchDetachedFromWindow will cause havoc on return. if (immediate && !mIsInTraversal) { doDie(); + return false; + } + + if (!mIsDrawing) { + destroyHardwareRenderer(); } else { - if (!mIsDrawing) { - destroyHardwareRenderer(); - } else { - Log.e(TAG, "Attempting to destroy the window while drawing!\n" + - " window=" + this + ", title=" + mWindowAttributes.getTitle()); - } - mHandler.sendEmptyMessage(MSG_DIE); + Log.e(TAG, "Attempting to destroy the window while drawing!\n" + + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } + mHandler.sendEmptyMessage(MSG_DIE); + return true; } void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { + if (mRemoved) { + return; + } + mRemoved = true; if (mAdded) { dispatchDetachedFromWindow(); } @@ -5181,13 +5278,14 @@ public final class ViewRootImpl implements ViewParent, } catch (RemoteException e) { } } - + mSurface.release(); } } mAdded = false; } + WindowManagerGlobal.getInstance().doRemoveView(this); } public void requestUpdateConfiguration(Configuration config) { @@ -5203,6 +5301,9 @@ public final class ViewRootImpl implements ViewParent, mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false); profileRendering(mAttachInfo.mHasWindowFocus); + // Media (used by sound effects) + mMediaDisabled = SystemProperties.getBoolean(PROPERTY_MEDIA_DISABLED, false); + // Hardware rendering if (mAttachInfo.mHardwareRenderer != null) { if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) { @@ -5518,8 +5619,8 @@ public final class ViewRootImpl implements ViewParent, final class InvalidateOnAnimationRunnable implements Runnable { private boolean mPosted; - private ArrayList mViews = new ArrayList(); - private ArrayList mViewRects = + private final ArrayList mViews = new ArrayList(); + private final ArrayList mViewRects = new ArrayList(); private View[] mTempViews; private AttachInfo.InvalidateInfo[] mTempViewRects; @@ -5632,8 +5733,8 @@ public final class ViewRootImpl implements ViewParent, mInvalidateOnAnimationRunnable.removeView(view); } - public void dispatchKey(KeyEvent event) { - Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY, event); + public void dispatchInputEvent(InputEvent event) { + Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, event); msg.setAsynchronous(true); mHandler.sendMessage(msg); } @@ -5663,7 +5764,7 @@ public final class ViewRootImpl implements ViewParent, flags, event.getSource(), null); fallbackAction.recycle(); - dispatchKey(fallbackEvent); + dispatchInputEvent(fallbackEvent); } } } @@ -5739,20 +5840,12 @@ public final class ViewRootImpl implements ViewParent, * This event is send at most once every * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ - private void postSendWindowContentChangedCallback(View source) { + private void postSendWindowContentChangedCallback(View source, int changeType) { if (mSendWindowContentChangedAccessibilityEvent == null) { mSendWindowContentChangedAccessibilityEvent = new SendWindowContentChangedAccessibilityEvent(); } - View oldSource = mSendWindowContentChangedAccessibilityEvent.mSource; - if (oldSource == null) { - mSendWindowContentChangedAccessibilityEvent.mSource = source; - mHandler.postDelayed(mSendWindowContentChangedAccessibilityEvent, - ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); - } else { - mSendWindowContentChangedAccessibilityEvent.mSource = - getCommonPredecessor(oldSource, source); - } + mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType); } /** @@ -5765,20 +5858,25 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public boolean showContextMenuForChild(View originalView) { return false; } + @Override public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) { return null; } + @Override public void createContextMenu(ContextMenu menu) { } + @Override public void childDrawableStateChanged(View child) { } + @Override public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { if (mView == null) { return false; @@ -5819,8 +5917,8 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void childAccessibilityStateChanged(View child) { - postSendWindowContentChangedCallback(child); + public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { + postSendWindowContentChangedCallback(source, changeType); } @Override @@ -5910,10 +6008,12 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { // ViewAncestor never intercepts touch event, so this can be a no-op } + @Override public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { final boolean scrolled = scrollToRectOrFocus(rectangle, immediate); if (rectangle != null) { @@ -5929,10 +6029,16 @@ public final class ViewRootImpl implements ViewParent, return scrolled; } + @Override public void childHasTransientStateChanged(View child, boolean hasTransientState) { // Do nothing. } + void changeCanvasOpacity(boolean opaque) { + // TODO(romainguy): recreate Canvas (software or hardware) to reflect the opacity change. + Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque); + } + class TakenSurfaceHolder extends BaseSurfaceHolder { @Override public boolean onAllowLockCanvas() { @@ -5944,20 +6050,23 @@ public final class ViewRootImpl implements ViewParent, // Not currently interesting -- from changing between fixed and layout size. } + @Override public void setFormat(int format) { ((RootViewSurfaceTaker)mView).setSurfaceFormat(format); } + @Override public void setType(int type) { ((RootViewSurfaceTaker)mView).setSurfaceType(type); } - + @Override public void onUpdateSurface() { // We take care of format and type changes on our own. throw new IllegalStateException("Shouldn't be here"); } + @Override public boolean isCreating() { return mIsCreating; } @@ -5967,7 +6076,8 @@ public final class ViewRootImpl implements ViewParent, throw new UnsupportedOperationException( "Currently only support sizing from layout"); } - + + @Override public void setKeepScreenOn(boolean screenOn) { ((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn); } @@ -5982,6 +6092,7 @@ public final class ViewRootImpl implements ViewParent, mWindowSession = viewAncestor.mWindowSession; } + @Override public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig) { final ViewRootImpl viewAncestor = mViewAncestor.get(); @@ -5999,6 +6110,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void dispatchAppVisibility(boolean visible) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -6006,6 +6118,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void dispatchScreenState(boolean on) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -6013,6 +6126,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void dispatchGetNewSurface() { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -6020,6 +6134,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -6036,6 +6151,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -6066,14 +6182,16 @@ public final class ViewRootImpl implements ViewParent, } } } - + + @Override public void closeSystemDialogs(String reason) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchCloseSystemDialogs(reason); } } - + + @Override public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, boolean sync) { if (sync) { @@ -6084,6 +6202,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync) { if (sync) { @@ -6095,6 +6214,7 @@ public final class ViewRootImpl implements ViewParent, } /* Drag/drop */ + @Override public void dispatchDragEvent(DragEvent event) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -6102,6 +6222,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, int localValue, int localChanges) { final ViewRootImpl viewAncestor = mViewAncestor.get(); @@ -6111,6 +6232,7 @@ public final class ViewRootImpl implements ViewParent, } } + @Override public void doneAnimating() { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -6125,50 +6247,63 @@ public final class ViewRootImpl implements ViewParent, } } - private SurfaceHolder mHolder = new SurfaceHolder() { + private final SurfaceHolder mHolder = new SurfaceHolder() { // we only need a SurfaceHolder for opengl. it would be nice // to implement everything else though, especially the callback // support (opengl doesn't make use of it right now, but eventually // will). + @Override public Surface getSurface() { return mSurface; } + @Override public boolean isCreating() { return false; } + @Override public void addCallback(Callback callback) { } + @Override public void removeCallback(Callback callback) { } + @Override public void setFixedSize(int width, int height) { } + @Override public void setSizeFromLayout() { } + @Override public void setFormat(int format) { } + @Override public void setType(int type) { } + @Override public void setKeepScreenOn(boolean screenOn) { } + @Override public Canvas lockCanvas() { return null; } + @Override public Canvas lockCanvas(Rect dirty) { return null; } + @Override public void unlockCanvasAndPost(Canvas canvas) { } + @Override public Rect getSurfaceFrame() { return null; } @@ -6263,6 +6398,7 @@ public final class ViewRootImpl implements ViewParent, */ final class AccessibilityInteractionConnectionManager implements AccessibilityStateChangeListener { + @Override public void onAccessibilityStateChanged(boolean enabled) { if (enabled) { ensureConnection(); @@ -6435,13 +6571,48 @@ public final class ViewRootImpl implements ViewParent, } private class SendWindowContentChangedAccessibilityEvent implements Runnable { + private int mChangeTypes = 0; + public View mSource; + public long mLastEventTimeMillis; + @Override public void run() { + // The accessibility may be turned off while we were waiting so check again. + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + mLastEventTimeMillis = SystemClock.uptimeMillis(); + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(mChangeTypes); + mSource.sendAccessibilityEventUnchecked(event); + } else { + mLastEventTimeMillis = 0; + } + // In any case reset to initial state. + mSource.resetSubtreeAccessibilityStateChanged(); + mSource = null; + mChangeTypes = 0; + } + + public void runOrPost(View source, int changeType) { if (mSource != null) { - mSource.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - mSource.resetAccessibilityStateChanged(); - mSource = null; + // If there is no common predecessor, then mSource points to + // a removed view, hence in this case always prefer the source. + View predecessor = getCommonPredecessor(mSource, source); + mSource = (predecessor != null) ? predecessor : source; + mChangeTypes |= changeType; + return; + } + mSource = source; + mChangeTypes = changeType; + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; + final long minEventIntevalMillis = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastMillis >= minEventIntevalMillis) { + mSource.removeCallbacks(this); + run(); + } else { + mSource.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); } } } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 072c95f491f770759959b24333c0daa0266b392b..a9444b44760ad4bdba987281a0867c2e422b20c7 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -241,11 +241,18 @@ public final class ViewTreeObserver { mTouchableInsets = TOUCHABLE_INSETS_FRAME; } + boolean isEmpty() { + return contentInsets.isEmpty() + && visibleInsets.isEmpty() + && touchableRegion.isEmpty() + && mTouchableInsets == TOUCHABLE_INSETS_FRAME; + } + @Override public int hashCode() { - int result = contentInsets != null ? contentInsets.hashCode() : 0; - result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0); - result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0); + int result = contentInsets.hashCode(); + result = 31 * result + visibleInsets.hashCode(); + result = 31 * result + touchableRegion.hashCode(); result = 31 * result + mTouchableInsets; return result; } @@ -813,6 +820,13 @@ public final class ViewTreeObserver { } } + /** + * Returns whether there are listeners for on pre-draw events. + */ + final boolean hasOnPreDrawListeners() { + return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0; + } + /** * Notifies registered listeners that the drawing pass is about to start. If a * listener returns true, then the drawing pass is canceled and rescheduled. This can @@ -983,6 +997,8 @@ public final class ViewTreeObserver { mStart = false; if (mDataCopy != null) { mData = mDataCopy; + mAccess.mData.clear(); + mAccess.mSize = 0; } mDataCopy = null; } diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index 9c00b7f8b64cb96e525d2556a3eec3993d42fa05..f0e6677d016b7eb9afc0dbad09d4cbc7b6d7a96a 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -32,6 +32,7 @@ import android.media.AudioService; import android.media.AudioSystem; import android.media.RingtoneManager; import android.media.ToneGenerator; +import android.media.VolumeController; import android.net.Uri; import android.os.Handler; import android.os.Message; @@ -55,7 +56,8 @@ import java.util.HashMap; * * @hide */ -public class VolumePanel extends Handler implements OnSeekBarChangeListener, View.OnClickListener +public class VolumePanel extends Handler implements OnSeekBarChangeListener, View.OnClickListener, + VolumeController { private static final String TAG = "VolumePanel"; private static boolean LOGD = false; diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 06974d31e86e1f94a9368699e9f6f105453afacf..b3a069935d99a35d0980571840caa7fdccfa093d 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -708,6 +708,11 @@ public abstract class Window { public void addFlags(int flags) { setFlags(flags, flags); } + + /** @hide */ + public void addPrivateFlags(int flags) { + setPrivateFlags(flags, flags); + } /** * Convenience function to clear the flag bits as specified in flags, as @@ -751,6 +756,14 @@ public abstract class Window { } } + private void setPrivateFlags(int flags, int mask) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.privateFlags = (attrs.privateFlags & ~mask) | (flags & mask); + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + /** * Set the amount of dim behind the window when using * {@link WindowManager.LayoutParams#FLAG_DIM_BEHIND}. This overrides @@ -1256,4 +1269,52 @@ public abstract class Window { * @param mask Flags specifying which options should be modified. Others will remain unchanged. */ public void setUiOptions(int uiOptions, int mask) { } + + /** + * Set the primary icon for this window. + * + * @param resId resource ID of a drawable to set + */ + public void setIcon(int resId) { } + + /** + * Set the default icon for this window. + * This will be overridden by any other icon set operation which could come from the + * theme or another explicit set. + * + * @hide + */ + public void setDefaultIcon(int resId) { } + + /** + * Set the logo for this window. A logo is often shown in place of an + * {@link #setIcon(int) icon} but is generally wider and communicates window title information + * as well. + * + * @param resId resource ID of a drawable to set + */ + public void setLogo(int resId) { } + + /** + * Set the default logo for this window. + * This will be overridden by any other logo set operation which could come from the + * theme or another explicit set. + * + * @hide + */ + public void setDefaultLogo(int resId) { } + + /** + * Set focus locally. The window should have the + * {@link WindowManager.LayoutParams#FLAG_LOCAL_FOCUS_MODE} flag set already. + * @param hasFocus Whether this window has focus or not. + * @param inTouchMode Whether this window is in touch mode or not. + */ + public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { } + + /** + * Inject an event to window locally. + * @param event A key or touch event to inject to this window. + */ + public void injectInputEvent(InputEvent event) { } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 541c503545d3932576e578c57330393adeb19a8b..0ce4da5c4dc5c184039cc8368e4595211bf126dd 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -219,7 +219,8 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_DREAM, to = "TYPE_DREAM"), @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"), @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"), - @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY") + @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"), + @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION") }) public int type; @@ -527,6 +528,20 @@ public interface WindowManager extends ViewManager { */ public static final int TYPE_RECENTS_OVERLAY = FIRST_SYSTEM_WINDOW+28; + + /** + * Window type: keyguard scrim window. Shows if keyguard needs to be restarted. + * In multiuser systems shows on all users' windows. + * @hide + */ + public static final int TYPE_KEYGUARD_SCRIM = FIRST_SYSTEM_WINDOW+29; + + /** + * Window type: Window for Presentation on top of private + * virtual display. + */ + public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30; + /** * End of types of system windows. */ @@ -823,9 +838,56 @@ public interface WindowManager extends ViewManager { */ public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000; + /** + * Window flag: request a translucent status bar with minimal system-provided + * background protection. + * + *

          This flag can be controlled in your theme through the + * {@link android.R.attr#windowTranslucentStatus} attribute; this attribute + * is automatically set for you in the standard translucent decor themes + * such as + * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor}, + * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor}, + * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and + * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.

          + * + *

          When this flag is enabled for a window, it automatically sets + * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and + * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.

          + */ + public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000; + + /** + * Window flag: request a translucent navigation bar with minimal system-provided + * background protection. + * + *

          This flag can be controlled in your theme through the + * {@link android.R.attr#windowTranslucentNavigation} attribute; this attribute + * is automatically set for you in the standard translucent decor themes + * such as + * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor}, + * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor}, + * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and + * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.

          + * + *

          When this flag is enabled for a window, it automatically sets + * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and + * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.

          + */ + public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000; + // ----- HIDDEN FLAGS. // These start at the high bit and go down. + /** + * Flag for a window in local focus mode. + * Window in local focus mode can control focus independent of window manager using + * {@link Window#setLocalFocus(boolean, boolean)}. + * Usually window in this mode will not get touch/key events from window manager, but will + * get events only via local injection using {@link Window#injectInputEvent(InputEvent)}. + */ + public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000; + /** Window flag: Enable touches to slide out of a window into neighboring * windows in mid-gesture instead of being captured for the duration of * the gesture. @@ -836,7 +898,7 @@ public interface WindowManager extends ViewManager { * * {@hide} */ - public static final int FLAG_SLIPPERY = 0x04000000; + public static final int FLAG_SLIPPERY = 0x20000000; /** * Flag for a window belonging to an activity that responds to {@link KeyEvent#KEYCODE_MENU} @@ -849,20 +911,8 @@ public interface WindowManager extends ViewManager { * * {@hide} */ - public static final int FLAG_NEEDS_MENU_KEY = 0x08000000; + public static final int FLAG_NEEDS_MENU_KEY = 0x40000000; - /** Window flag: special flag to limit the size of the window to be - * original size ([320x480] x density). Used to create window for applications - * running under compatibility mode. - * - * {@hide} */ - public static final int FLAG_COMPATIBLE_WINDOW = 0x20000000; - - /** Window flag: a special option intended for system dialogs. When - * this flag is set, the window will demand focus unconditionally when - * it is created. - * {@hide} */ - public static final int FLAG_SYSTEM_ERROR = 0x40000000; /** * Various behavioral options/flags. Default is none. @@ -890,6 +940,7 @@ public interface WindowManager extends ViewManager { * @see #FLAG_DISMISS_KEYGUARD * @see #FLAG_SPLIT_TOUCH * @see #FLAG_HARDWARE_ACCELERATED + * @see #FLAG_LOCAL_FOCUS_MODE */ @ViewDebug.ExportedProperty(flagMapping = { @ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, @@ -941,7 +992,13 @@ public interface WindowManager extends ViewManager { @ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH, name = "FLAG_SPLIT_TOUCH"), @ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED, - name = "FLAG_HARDWARE_ACCELERATED") + name = "FLAG_HARDWARE_ACCELERATED"), + @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE, + name = "FLAG_LOCAL_FOCUS_MODE"), + @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS, + name = "FLAG_TRANSLUCENT_STATUS"), + @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION, + name = "FLAG_TRANSLUCENT_NAVIGATION") }) public int flags; @@ -1018,6 +1075,24 @@ public interface WindowManager extends ViewManager { * {@hide} */ public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040; + /** Window flag: special flag to limit the size of the window to be + * original size ([320x480] x density). Used to create window for applications + * running under compatibility mode. + * + * {@hide} */ + public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080; + + /** Window flag: a special option intended for system dialogs. When + * this flag is set, the window will demand focus unconditionally when + * it is created. + * {@hide} */ + public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100; + + /** Window flag: maintain the previous translucent decor state when this window + * becomes top-most. + * {@hide} */ + public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200; + /** * Control flags that are private to the platform. * @hide @@ -1548,6 +1623,8 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<18; /** {@hide} */ + public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19; + /** {@hide} */ public static final int EVERYTHING_CHANGED = 0xffffffff; // internal buffer to backup/restore parameters under compatibility mode. @@ -1593,6 +1670,10 @@ public interface WindowManager extends ViewManager { changes |= TYPE_CHANGED; } if (flags != o.flags) { + final int diff = flags ^ o.flags; + if ((diff & (FLAG_TRANSLUCENT_STATUS | FLAG_TRANSLUCENT_NAVIGATION)) != 0) { + changes |= TRANSLUCENT_FLAGS_CHANGED; + } flags = o.flags; changes |= FLAGS_CHANGED; } @@ -1726,6 +1807,9 @@ public interface WindowManager extends ViewManager { sb.append(" fl=#"); sb.append(Integer.toHexString(flags)); if (privateFlags != 0) { + if ((privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) { + sb.append(" compatible=true"); + } sb.append(" pfl=0x").append(Integer.toHexString(privateFlags)); } if (format != PixelFormat.OPAQUE) { @@ -1756,9 +1840,6 @@ public interface WindowManager extends ViewManager { sb.append(" rotAnim="); sb.append(rotationAnimation); } - if ((flags & FLAG_COMPATIBLE_WINDOW) != 0) { - sb.append(" compatible=true"); - } if (systemUiVisibility != 0) { sb.append(" sysui=0x"); sb.append(Integer.toHexString(systemUiVisibility)); diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 0ff46e96628899857f160b642bb1777ffa0b6f70..96c0ed2674f562175edecde82a63d690c314d805 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -22,17 +22,19 @@ import android.content.ComponentCallbacks2; import android.content.res.Configuration; import android.opengl.ManagedEGLContext; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.util.AndroidRuntimeException; +import android.util.ArraySet; import android.util.Log; import android.view.inputmethod.InputMethodManager; +import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; +import java.util.ArrayList; /** * Provides low-level communication with the system window manager for @@ -107,9 +109,11 @@ public final class WindowManagerGlobal { private final Object mLock = new Object(); - private View[] mViews; - private ViewRootImpl[] mRoots; - private WindowManager.LayoutParams[] mParams; + private final ArrayList mViews = new ArrayList(); + private final ArrayList mRoots = new ArrayList(); + private final ArrayList mParams = + new ArrayList(); + private final ArraySet mDyingViews = new ArraySet(); private boolean mNeedsEglTerminate; private Runnable mSystemPropertyUpdater; @@ -162,11 +166,10 @@ public final class WindowManagerGlobal { public String[] getViewRootNames() { synchronized (mLock) { - if (mRoots == null) return new String[0]; - String[] mViewRoots = new String[mRoots.length]; - int i = 0; - for (ViewRootImpl root : mRoots) { - mViewRoots[i++] = getWindowName(root); + final int numRoots = mRoots.size(); + String[] mViewRoots = new String[numRoots]; + for (int i = 0; i < numRoots; ++i) { + mViewRoots[i] = getWindowName(mRoots.get(i)); } return mViewRoots; } @@ -174,8 +177,8 @@ public final class WindowManagerGlobal { public View getRootView(String name) { synchronized (mLock) { - if (mRoots == null) return null; - for (ViewRootImpl root : mRoots) { + for (int i = mRoots.size() - 1; i >= 0; --i) { + final ViewRootImpl root = mRoots.get(i); if (name.equals(getWindowName(root))) return root.getView(); } } @@ -209,8 +212,8 @@ public final class WindowManagerGlobal { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { - for (ViewRootImpl viewRoot : mRoots) { - viewRoot.loadSystemProperties(); + for (int i = mRoots.size() - 1; i >= 0; --i) { + mRoots.get(i).loadSystemProperties(); } } } @@ -220,18 +223,24 @@ public final class WindowManagerGlobal { int index = findViewLocked(view, false); if (index >= 0) { - throw new IllegalStateException("View " + view - + " has already been added to the window manager."); + if (mDyingViews.contains(view)) { + // Don't wait for MSG_DIE to make it's way through root's queue. + mRoots.get(index).doDie(); + } else { + throw new IllegalStateException("View " + view + + " has already been added to the window manager."); + } + // The previous removeView() had not completed executing. Now it has. } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { - final int count = mViews != null ? mViews.length : 0; - for (int i=0; i 0) { - if (index > 0) { - System.arraycopy(src, 0, dst, 0, index); - } - if (index < dst.length) { - System.arraycopy(src, index+1, dst, index, src.length-index-1); + void doRemoveView(ViewRootImpl root) { + synchronized (mLock) { + final int index = mRoots.indexOf(root); + if (index >= 0) { + mRoots.remove(index); + mParams.remove(index); + final View view = mViews.remove(index); + mDyingViews.remove(view); } } } private int findViewLocked(View view, boolean required) { - if (mViews != null) { - final int count = mViews.length; - for (int i = 0; i < count; i++) { - if (mViews[i] == view) { - return i; - } - } - } - if (required) { - throw new IllegalArgumentException("View not attached to window manager"); + final int index = mViews.indexOf(view); + if (required && index < 0) { + throw new IllegalArgumentException("View=" + view + " not attached to window manager"); } - return -1; + return index; } public void startTrimMemory(int level) { @@ -418,10 +382,8 @@ public final class WindowManagerGlobal { // Destroy all hardware surfaces and resources associated to // known windows synchronized (mLock) { - if (mViews == null) return; - int count = mViews.length; - for (int i = 0; i < count; i++) { - mRoots[i].terminateHardwareResources(); + for (int i = mRoots.size() - 1; i >= 0; --i) { + mRoots.get(i).destroyHardwareResources(); } } // Force a full memory flush @@ -445,64 +407,60 @@ public final class WindowManagerGlobal { public void trimLocalMemory() { synchronized (mLock) { - if (mViews == null) return; - int count = mViews.length; - for (int i = 0; i < count; i++) { - mRoots[i].destroyHardwareLayers(); + for (int i = mRoots.size() - 1; i >= 0; --i) { + mRoots.get(i).destroyHardwareLayers(); } } } public void dumpGfxInfo(FileDescriptor fd) { FileOutputStream fout = new FileOutputStream(fd); - PrintWriter pw = new PrintWriter(fout); + PrintWriter pw = new FastPrintWriter(fout); try { synchronized (mLock) { - if (mViews != null) { - final int count = mViews.length; + final int count = mViews.size(); - pw.println("Profile data in ms:"); + pw.println("Profile data in ms:"); - for (int i = 0; i < count; i++) { - ViewRootImpl root = mRoots[i]; - String name = getWindowName(root); - pw.printf("\n\t%s", name); + for (int i = 0; i < count; i++) { + ViewRootImpl root = mRoots.get(i); + String name = getWindowName(root); + pw.printf("\n\t%s", name); - HardwareRenderer renderer = - root.getView().mAttachInfo.mHardwareRenderer; - if (renderer != null) { - renderer.dumpGfxInfo(pw); - } + HardwareRenderer renderer = + root.getView().mAttachInfo.mHardwareRenderer; + if (renderer != null) { + renderer.dumpGfxInfo(pw); } + } - pw.println("\nView hierarchy:\n"); - - int viewsCount = 0; - int displayListsSize = 0; - int[] info = new int[2]; + pw.println("\nView hierarchy:\n"); - for (int i = 0; i < count; i++) { - ViewRootImpl root = mRoots[i]; - root.dumpGfxInfo(info); + int viewsCount = 0; + int displayListsSize = 0; + int[] info = new int[2]; - String name = getWindowName(root); - pw.printf(" %s\n %d views, %.2f kB of display lists", - name, info[0], info[1] / 1024.0f); - HardwareRenderer renderer = - root.getView().mAttachInfo.mHardwareRenderer; - if (renderer != null) { - pw.printf(", %d frames rendered", renderer.getFrameCount()); - } - pw.printf("\n\n"); + for (int i = 0; i < count; i++) { + ViewRootImpl root = mRoots.get(i); + root.dumpGfxInfo(info); - viewsCount += info[0]; - displayListsSize += info[1]; + String name = getWindowName(root); + pw.printf(" %s\n %d views, %.2f kB of display lists", + name, info[0], info[1] / 1024.0f); + HardwareRenderer renderer = + root.getView().mAttachInfo.mHardwareRenderer; + if (renderer != null) { + pw.printf(", %d frames rendered", renderer.getFrameCount()); } + pw.printf("\n\n"); - pw.printf("\nTotal ViewRootImpl: %d\n", count); - pw.printf("Total Views: %d\n", viewsCount); - pw.printf("Total DisplayList: %.2f kB\n\n", displayListsSize / 1024.0f); + viewsCount += info[0]; + displayListsSize += info[1]; } + + pw.printf("\nTotal ViewRootImpl: %d\n", count); + pw.printf("Total Views: %d\n", viewsCount); + pw.printf("Total DisplayList: %.2f kB\n\n", displayListsSize / 1024.0f); } } finally { pw.flush(); @@ -516,13 +474,11 @@ public final class WindowManagerGlobal { public void setStoppedState(IBinder token, boolean stopped) { synchronized (mLock) { - if (mViews != null) { - int count = mViews.length; - for (int i=0; i < count; i++) { - if (token == null || mParams[i].token == token) { - ViewRootImpl root = mRoots[i]; - root.setStopped(stopped); - } + int count = mViews.size(); + for (int i = 0; i < count; i++) { + if (token == null || mParams.get(i).token == token) { + ViewRootImpl root = mRoots.get(i); + root.setStopped(stopped); } } } @@ -530,12 +486,25 @@ public final class WindowManagerGlobal { public void reportNewConfiguration(Configuration config) { synchronized (mLock) { - if (mViews != null) { - int count = mViews.length; - config = new Configuration(config); - for (int i=0; i < count; i++) { - ViewRootImpl root = mRoots[i]; - root.requestUpdateConfiguration(config); + int count = mViews.size(); + config = new Configuration(config); + for (int i=0; i < count; i++) { + ViewRootImpl root = mRoots.get(i); + root.requestUpdateConfiguration(config); + } + } + } + + /** @hide */ + public void changeCanvasOpacity(IBinder token, boolean opaque) { + if (token == null) { + return; + } + synchronized (mLock) { + for (int i = mParams.size() - 1; i >= 0; --i) { + if (mParams.get(i).token == token) { + mRoots.get(i).changeCanvasOpacity(opaque); + return; } } } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index c0044b662b28b72c15d27d68e3ca9bb2bcee1e34..c5a1b86c0635acd8e631555f89ebef104ac8cf24 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -165,9 +165,11 @@ public interface WindowManagerPolicy { * This can be used as a hint for scrolling (avoiding resizing) * the window to make certain that parts of its content * are visible. + * @param decorFrame The decor frame specified by policy specific to this window, + * to use for proper cropping during animation. */ public void computeFrameLw(Rect parentFrame, Rect displayFrame, - Rect overlayFrame, Rect contentFrame, Rect visibleFrame); + Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame); /** * Retrieve the current frame of the window that has been assigned by @@ -399,19 +401,14 @@ public interface WindowManagerPolicy { */ public FakeWindow addFakeWindow(Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, - String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, - boolean hasFocus, boolean touchFullscreen); + String name, int windowType, int layoutParamsFlags, int layoutParamsPrivateFlags, + boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen); /** * Returns a code that describes the current state of the lid switch. */ public int getLidState(); - /** - * Creates an input channel that will receive all input from the input dispatcher. - */ - public InputChannel monitorInput(String name); - /** * Switch the keyboard layout for the given device. * Direction should be +1 or -1 to go to the next or previous keyboard layout. @@ -420,6 +417,26 @@ public interface WindowManagerPolicy { public void shutdown(boolean confirm); public void rebootSafeMode(boolean confirm); + + /** + * Return the window manager lock needed to correctly call "Lw" methods. + */ + public Object getWindowManagerLock(); + + /** Register a system listener for touch events */ + void registerPointerEventListener(PointerEventListener listener); + + /** Unregister a system listener for touch events */ + void unregisterPointerEventListener(PointerEventListener listener); + } + + public interface PointerEventListener { + /** + * 1. onPointerEvent will be called on the service.UiThread. + * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a + * copy() must be made and the copy must be recycled. + **/ + public void onPointerEvent(MotionEvent motionEvent); } /** Window has been added to the screen. */ @@ -462,6 +479,11 @@ public interface WindowManagerPolicy { public void init(Context context, IWindowManager windowManager, WindowManagerFuncs windowManagerFuncs); + /** + * @return true if com.android.internal.R.bool#config_forceDefaultOrientation is true. + */ + public boolean isDefaultOrientationForced(); + /** * Called by window manager once it has the initial, default native * display dimensions. @@ -562,12 +584,6 @@ public interface WindowManagerPolicy { */ public int getAboveUniverseLayer(); - /** - * Return true if the policy desires a full unified system nav bar. Otherwise, - * it is a phone-style status bar with optional nav bar. - */ - public boolean hasSystemNavBar(); - /** * Return the display width available after excluding any screen * decorations that can never be removed. That is, system bar or @@ -637,7 +653,7 @@ public interface WindowManagerPolicy { */ public View addStartingWindow(IBinder appToken, String packageName, int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, - int labelRes, int icon, int windowFlags); + int labelRes, int icon, int logo, int windowFlags); /** * Called when the first window of an application has been displayed, while @@ -801,19 +817,18 @@ public interface WindowManagerPolicy { int displayRotation); /** - * Return the rectangle of the screen currently covered by system decorations. - * This will be called immediately after {@link #layoutWindowLw}. It can - * fill in the rectangle to indicate any part of the screen that it knows - * for sure is covered by system decor such as the status bar. The rectangle - * is initially set to the actual size of the screen, indicating nothing is - * covered. + * Returns the bottom-most layer of the system decor, above which no policy decor should + * be applied. + */ + public int getSystemDecorLayerLw(); + + /** + * Return the rectangle of the screen that is available for applications to run in. + * This will be called immediately after {@link #beginLayoutLw}. * - * @param systemRect The rectangle of the screen that is not covered by - * system decoration. - * @return Returns the layer above which the system rectangle should - * not be applied. + * @param r The rectangle to be filled with the boundaries available to applications. */ - public int getSystemDecorRectLw(Rect systemRect); + public void getContentRectLw(Rect r); /** * Called for each window attached to the window manager as layout is @@ -1153,12 +1168,6 @@ public interface WindowManagerPolicy { */ public void dump(String prefix, PrintWriter writer, String[] args); - /** - * Ask keyguard to invoke the assist intent after dismissing keyguard - * {@link android.content.Intent#ACTION_ASSIST} - */ - public void showAssistant(); - /** * Returns whether a given window type can be magnified. * @@ -1177,4 +1186,11 @@ public interface WindowManagerPolicy { * @return True if the window is a top level one. */ public boolean isTopLevelWindow(int windowType); + + /** + * Sets the current touch exploration state. + * + * @param enabled Whether touch exploration is enabled. + */ + public void setTouchExplorationEnabled(boolean enabled); } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index dbeca1f9bab1638f3a3a65f470ca5e9eae0b3706..f635eee474434d6c7011705b989399e14a362449 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -326,6 +326,7 @@ import java.util.List; * Properties:
          *
            *
          • {@link #getEventType()} - The type of the event.
          • + *
          • {@link #getContentChangeTypes()} - The type of content changes.
          • *
          • {@link #getSource()} - The source info (for registered clients).
          • *
          • {@link #getClassName()} - The class name of the source.
          • *
          • {@link #getPackageName()} - The package name of the source.
          • @@ -660,6 +661,30 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000; + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * The type of change is not defined. + */ + public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * A node in the subtree rooted at the source node was added or removed. + */ + public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * The node's text changed. + */ + public static final int CONTENT_CHANGE_TYPE_TEXT = 0x00000002; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * The node's content description changed. + */ + public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; + /** * Mask for {@link AccessibilityEvent} all types. * @@ -695,6 +720,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private long mEventTime; int mMovementGranularity; int mAction; + int mContentChangeTypes; private final ArrayList mRecords = new ArrayList(); @@ -714,6 +740,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mEventType = event.mEventType; mMovementGranularity = event.mMovementGranularity; mAction = event.mAction; + mContentChangeTypes = event.mContentChangeTypes; mEventTime = event.mEventTime; mPackageName = event.mPackageName; } @@ -776,6 +803,36 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par return mEventType; } + /** + * Gets the bit mask of change types signaled by an + * {@link #TYPE_WINDOW_CONTENT_CHANGED} event. A single event may represent + * multiple change types. + * + * @return The bit mask of change types. One or more of: + *
              + *
            • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION} + *
            • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE} + *
            • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT} + *
            • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED} + *
            + */ + public int getContentChangeTypes() { + return mContentChangeTypes; + } + + /** + * Sets the bit mask of node tree changes signaled by an + * {@link #TYPE_WINDOW_CONTENT_CHANGED} event. + * + * @param changeTypes The bit mask of change types. + * @throws IllegalStateException If called from an AccessibilityService. + * @see #getContentChangeTypes() + */ + public void setContentChangeTypes(int changeTypes) { + enforceNotSealed(); + mContentChangeTypes = changeTypes; + } + /** * Sets the event type. * @@ -853,10 +910,20 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Sets the performed action that triggered this event. + *

            + * Valid actions are defined in {@link AccessibilityNodeInfo}: + *

              + *
            • {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS} + *
            • {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS} + *
            • {@link AccessibilityNodeInfo#ACTION_CLEAR_FOCUS} + *
            • {@link AccessibilityNodeInfo#ACTION_CLEAR_SELECTION} + *
            • {@link AccessibilityNodeInfo#ACTION_CLICK} + *
            • etc. + *
            * * @param action The action. - * * @throws IllegalStateException If called from an AccessibilityService. + * @see AccessibilityNodeInfo#performAction(int) */ public void setAction(int action) { enforceNotSealed(); @@ -943,6 +1010,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mEventType = 0; mMovementGranularity = 0; mAction = 0; + mContentChangeTypes = 0; mPackageName = null; mEventTime = 0; while (!mRecords.isEmpty()) { @@ -961,6 +1029,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mEventType = parcel.readInt(); mMovementGranularity = parcel.readInt(); mAction = parcel.readInt(); + mContentChangeTypes = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); mConnectionId = parcel.readInt(); @@ -1013,6 +1082,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(mEventType); parcel.writeInt(mMovementGranularity); parcel.writeInt(mAction); + parcel.writeInt(mContentChangeTypes); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); parcel.writeInt(mConnectionId); @@ -1074,6 +1144,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append(super.toString()); if (DEBUG) { builder.append("\n"); + builder.append("; ContentChangeTypes: ").append(mContentChangeTypes); builder.append("; sourceWindowId: ").append(mSourceWindowId); builder.append("; mSourceNodeId: ").append(mSourceNodeId); for (int i = 0; i < mRecords.size(); i++) { diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 84d7e720b31f83ec1564e851d6abc47a1a919871..139df3e57af01bb1e44324c5f87dd58736c9dc4b 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -163,7 +163,7 @@ public final class AccessibilityInteractionClient public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) { return findAccessibilityNodeInfoByAccessibilityId(connectionId, AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, - AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); + false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); } /** @@ -177,18 +177,22 @@ public final class AccessibilityInteractionClient * where to start the search. Use * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} * to start from the root. + * @param bypassCache Whether to bypass the cache while looking for the node. * @param prefetchFlags flags to guide prefetching. * @return An {@link AccessibilityNodeInfo} if found, null otherwise. */ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId, - int accessibilityWindowId, long accessibilityNodeId, int prefetchFlags) { + int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache, + int prefetchFlags) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { - AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get( - accessibilityNodeId); - if (cachedInfo != null) { - return cachedInfo; + if (!bypassCache) { + AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get( + accessibilityNodeId); + if (cachedInfo != null) { + return cachedInfo; + } } final int interactionId = mInteractionIdCounter.getAndIncrement(); final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( @@ -350,7 +354,7 @@ public final class AccessibilityInteractionClient } } catch (RemoteException re) { if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote findAccessibilityFocus", re); + Log.w(LOG_TAG, "Error while calling remote findFocus", re); } } return null; diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 732699b0b0f41c52280e099a2a0383064ea77004..00f4adb911bcd48c5c53916c643d00366319e2a6 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -16,14 +16,17 @@ package android.view.accessibility; +import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -88,25 +91,45 @@ public final class AccessibilityManager { boolean mIsTouchExplorationEnabled; - final CopyOnWriteArrayList mAccessibilityStateChangeListeners = - new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList + mAccessibilityStateChangeListeners = new CopyOnWriteArrayList< + AccessibilityStateChangeListener>(); + + private final CopyOnWriteArrayList + mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList< + TouchExplorationStateChangeListener>(); /** - * Listener for the system accessibility state. To listen for changes to the accessibility - * state on the device, implement this interface and register it with the system by - * calling {@link AccessibilityManager#addAccessibilityStateChangeListener - * addAccessibilityStateChangeListener()}. + * Listener for the system accessibility state. To listen for changes to the + * accessibility state on the device, implement this interface and register + * it with the system by calling {@link #addAccessibilityStateChangeListener}. */ public interface AccessibilityStateChangeListener { /** - * Called back on change in the accessibility state. + * Called when the accessibility enabled state changes. * * @param enabled Whether accessibility is enabled. */ public void onAccessibilityStateChanged(boolean enabled); } + /** + * Listener for the system touch exploration state. To listen for changes to + * the touch exploration state on the device, implement this interface and + * register it with the system by calling + * {@link #addTouchExplorationStateChangeListener}. + */ + public interface TouchExplorationStateChangeListener { + + /** + * Called when the touch exploration enabled state changes. + * + * @param enabled Whether touch exploration is enabled. + */ + public void onTouchExplorationStateChanged(boolean enabled); + } + final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { public void setState(int state) { mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget(); @@ -131,29 +154,6 @@ public final class AccessibilityManager { } } - /** - * Creates the singleton AccessibilityManager to be shared across users. This - * has to be called before the local AccessibilityManager is created to ensure - * it registers itself in the system correctly. - *

            - * Note: Calling this method requires INTERACT_ACROSS_USERS_FULL or - * INTERACT_ACROSS_USERS permission. - *

            - * @param context Context in which this manager operates. - * @throws IllegalStateException if not called before the local - * AccessibilityManager is instantiated. - * - * @hide - */ - public static void createAsSharedAcrossUsers(Context context) { - synchronized (sInstanceSync) { - if (sInstance != null) { - throw new IllegalStateException("AccessibilityManager already created."); - } - createSingletonInstance(context, UserHandle.USER_CURRENT); - } - } - /** * Get an AccessibilityManager instance (create one if necessary). * @@ -164,24 +164,26 @@ public final class AccessibilityManager { public static AccessibilityManager getInstance(Context context) { synchronized (sInstanceSync) { if (sInstance == null) { - createSingletonInstance(context, UserHandle.myUserId()); + final int userId; + if (Binder.getCallingUid() == Process.SYSTEM_UID + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS) + == PackageManager.PERMISSION_GRANTED + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + userId = UserHandle.USER_CURRENT; + } else { + userId = UserHandle.myUserId(); + } + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); + sInstance = new AccessibilityManager(context, service, userId); } } return sInstance; } - /** - * Creates the singleton instance. - * - * @param context Context in which this manager operates. - * @param userId The user id under which to operate. - */ - private static void createSingletonInstance(Context context, int userId) { - IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); - IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); - sInstance = new AccessibilityManager(context, service, userId); - } - /** * Create an instance. * @@ -381,40 +383,80 @@ public final class AccessibilityManager { } /** - * Sets the current state. + * Registers a {@link TouchExplorationStateChangeListener} for changes in + * the global touch exploration state of the system. * - * @param stateFlags The state flags. + * @param listener The listener. + * @return True if successfully registered. */ - private void setState(int stateFlags) { - final boolean accessibilityEnabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; - setAccessibilityState(accessibilityEnabled); - mIsTouchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; + public boolean addTouchExplorationStateChangeListener( + TouchExplorationStateChangeListener listener) { + return mTouchExplorationStateChangeListeners.add(listener); } /** - * Sets the enabled state. + * Unregisters a {@link TouchExplorationStateChangeListener}. * - * @param isEnabled The accessibility state. + * @param listener The listener. + * @return True if successfully unregistered. */ - private void setAccessibilityState(boolean isEnabled) { + public boolean removeTouchExplorationStateChangeListener( + TouchExplorationStateChangeListener listener) { + return mTouchExplorationStateChangeListeners.remove(listener); + } + + /** + * Sets the current state and notifies listeners, if necessary. + * + * @param stateFlags The state flags. + */ + private void setState(int stateFlags) { + final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; + final boolean touchExplorationEnabled = + (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; synchronized (mHandler) { - if (isEnabled != mIsEnabled) { - mIsEnabled = isEnabled; - notifyAccessibilityStateChanged(); + final boolean wasEnabled = mIsEnabled; + final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; + + // Ensure listeners get current state from isZzzEnabled() calls. + mIsEnabled = enabled; + mIsTouchExplorationEnabled = touchExplorationEnabled; + + if (wasEnabled != enabled) { + notifyAccessibilityStateChangedLh(); + } + + if (wasTouchExplorationEnabled != touchExplorationEnabled) { + notifyTouchExplorationStateChangedLh(); } } } /** * Notifies the registered {@link AccessibilityStateChangeListener}s. + *

            + * The caller must be locked on {@link #mHandler}. */ - private void notifyAccessibilityStateChanged() { + private void notifyAccessibilityStateChangedLh() { final int listenerCount = mAccessibilityStateChangeListeners.size(); for (int i = 0; i < listenerCount; i++) { mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled); } } + /** + * Notifies the registered {@link TouchExplorationStateChangeListener}s. + *

            + * The caller must be locked on {@link #mHandler}. + */ + private void notifyTouchExplorationStateChangedLh() { + final int listenerCount = mTouchExplorationStateChangeListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mTouchExplorationStateChangeListeners.get(i) + .onTouchExplorationStateChanged(mIsTouchExplorationEnabled); + } + } + /** * Adds an accessibility interaction connection interface for a given window. * @param windowToken The window token to which a connection is added. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index d9c9b69bfcedda834ec05d018348df6b0b02c24c..9fc37cfa679daefa631c0f0850b2367b41cb4ef3 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -21,6 +21,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.text.InputType; import android.util.Pools.SynchronizedPool; import android.util.SparseLongArray; import android.view.View; @@ -266,6 +267,23 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static final int ACTION_SET_SELECTION = 0x00020000; + /** + * Action to expand an expandable node. + */ + public static final int ACTION_EXPAND = 0x00040000; + + /** + * Action to collapse an expandable node. + */ + public static final int ACTION_COLLAPSE = 0x00080000; + + /** + * Action to dismiss a dismissable node. + */ + public static final int ACTION_DISMISS = 0x00100000; + + // Action arguments + /** * Argument for which movement granularity to be used when traversing the node text. *

            @@ -333,6 +351,8 @@ public class AccessibilityNodeInfo implements Parcelable { public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; + // Focus types + /** * The input focus. */ @@ -398,6 +418,14 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000; + private static final int BOOLEAN_PROPERTY_OPENS_POPUP = 0x00002000; + + private static final int BOOLEAN_PROPERTY_DISMISSABLE = 0x00004000; + + private static final int BOOLEAN_PROPERTY_MULTI_LINE = 0x00008000; + + private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000; + /** * Bits that provide the id of a virtual descendant of a view. */ @@ -482,9 +510,17 @@ public class AccessibilityNodeInfo implements Parcelable { private int mTextSelectionStart = UNDEFINED; private int mTextSelectionEnd = UNDEFINED; + private int mInputType = InputType.TYPE_NULL; + private int mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE; + + private Bundle mExtras; private int mConnectionId = UNDEFINED; + private RangeInfo mRangeInfo; + private CollectionInfo mCollectionInfo; + private CollectionItemInfo mCollectionItemInfo; + /** * Hide constructor from clients. */ @@ -594,16 +630,20 @@ public class AccessibilityNodeInfo implements Parcelable { * since it represents a view that is no longer in the view tree and should * be recycled. *

            + * + * @param bypassCache Whether to bypass the cache. * @return Whether the refresh succeeded. + * + * @hide */ - public boolean refresh() { + public boolean refresh(boolean bypassCache) { enforceSealed(); if (!canPerformRequestOverConnection(mSourceNodeId)) { return false; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId( - mConnectionId, mWindowId, mSourceNodeId, 0); + mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0); if (refreshedInfo == null) { return false; } @@ -612,6 +652,19 @@ public class AccessibilityNodeInfo implements Parcelable { return true; } + /** + * Refreshes this info with the latest state of the view it represents. + *

            + * Note: If this method returns false this info is obsolete + * since it represents a view that is no longer in the view tree and should + * be recycled. + *

            + * @return Whether the refresh succeeded. + */ + public boolean refresh() { + return refresh(false); + } + /** * @return The ids of the children. * @@ -652,7 +705,7 @@ public class AccessibilityNodeInfo implements Parcelable { final long childId = mChildNodeIds.get(index); AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, - childId, FLAG_PREFETCH_DESCENDANTS); + childId, false, FLAG_PREFETCH_DESCENDANTS); } /** @@ -878,7 +931,7 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mParentNodeId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1283,7 +1336,6 @@ public class AccessibilityNodeInfo implements Parcelable { * @throws IllegalStateException If called from an AccessibilityService. */ public void setScrollable(boolean scrollable) { - enforceNotSealed(); setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable); } @@ -1312,6 +1364,216 @@ public class AccessibilityNodeInfo implements Parcelable { setBooleanProperty(BOOLEAN_PROPERTY_EDITABLE, editable); } + /** + * Gets the collection info if the node is a collection. A collection + * child is always a collection item. + * + * @return The collection info. + */ + public CollectionInfo getCollectionInfo() { + return mCollectionInfo; + } + + /** + * Sets the collection info if the node is a collection. A collection + * child is always a collection item. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

            + * + * @param collectionInfo The collection info. + */ + public void setCollectionInfo(CollectionInfo collectionInfo) { + enforceNotSealed(); + mCollectionInfo = collectionInfo; + } + + /** + * Gets the collection item info if the node is a collection item. A collection + * item is always a child of a collection. + * + * @return The collection item info. + */ + public CollectionItemInfo getCollectionItemInfo() { + return mCollectionItemInfo; + } + + /** + * Sets the collection item info if the node is a collection item. A collection + * item is always a child of a collection. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

            + * + * @return collectionItem True if the node is an item. + */ + public void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) { + enforceNotSealed(); + mCollectionItemInfo = collectionItemInfo; + } + + /** + * Gets the range info if this node is a range. + * + * @return The range. + */ + public RangeInfo getRangeInfo() { + return mRangeInfo; + } + + /** + * Sets the range info if this node is a range. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

            + * + * @param rangeInfo The range info. + */ + public void setRangeInfo(RangeInfo rangeInfo) { + enforceNotSealed(); + mRangeInfo = rangeInfo; + } + + /** + * Gets if the content of this node is invalid. For example, + * a date is not well-formed. + * + * @return If the node content is invalid. + */ + public boolean isContentInvalid() { + return getBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID); + } + + /** + * Sets if the content of this node is invalid. For example, + * a date is not well-formed. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

            + * + * @param contentInvalid If the node content is invalid. + */ + public void setContentInvalid(boolean contentInvalid) { + setBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID, contentInvalid); + } + + /** + * Gets the node's live region mode. + *

            + * A live region is a node that contains information that is important for + * the user and when it changes the user should be notified. For example, + * in a login screen with a TextView that displays an "incorrect password" + * notification, that view should be marked as a live region with mode + * {@link View#ACCESSIBILITY_LIVE_REGION_POLITE}. + *

            + * It is the responsibility of the accessibility service to monitor + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events indicating + * changes to live region nodes and their children. + * + * @return The live region mode, or + * {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a + * live region. + * @see android.view.View#getAccessibilityLiveRegion() + */ + public int getLiveRegion() { + return mLiveRegion; + } + + /** + * Sets the node's live region mode. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * + * @param mode The live region mode, or + * {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a + * live region. + * @see android.view.View#setAccessibilityLiveRegion(int) + */ + public void setLiveRegion(int mode) { + enforceNotSealed(); + mLiveRegion = mode; + } + + /** + * Gets if the node is a multi line editable text. + * + * @return True if the node is multi line. + */ + public boolean isMultiLine() { + return getBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE); + } + + /** + * Sets if the node is a multi line editable text. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

            + * + * @param multiLine True if the node is multi line. + */ + public void setMultiLine(boolean multiLine) { + setBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE, multiLine); + } + + /** + * Gets if this node opens a popup or a dialog. + * + * @return If the the node opens a popup. + */ + public boolean canOpenPopup() { + return getBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP); + } + + /** + * Sets if this node opens a popup or a dialog. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

            + * + * @param opensPopup If the the node opens a popup. + */ + public void setCanOpenPopup(boolean opensPopup) { + enforceNotSealed(); + setBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP, opensPopup); + } + + /** + * Gets if the node can be dismissed. + * + * @return If the node can be dismissed. + */ + public boolean isDismissable() { + return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE); + } + + /** + * Sets if the node can be dismissed. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

            + * + * @param dismissable If the node can be dismissed. + */ + public void setDismissable(boolean dismissable) { + setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable); + } + /** * Gets the package this node comes from. * @@ -1470,7 +1732,7 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabelForId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mLabelForId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1527,7 +1789,7 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabeledById, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mLabeledById, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1599,6 +1861,53 @@ public class AccessibilityNodeInfo implements Parcelable { mTextSelectionEnd = end; } + /** + * Gets the input type of the source as defined by {@link InputType}. + * + * @return The input type. + */ + public int getInputType() { + return mInputType; + } + + /** + * Sets the input type of the source as defined by {@link InputType}. + *

            + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an + * AccessibilityService. + *

            + * + * @param inputType The input type. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setInputType(int inputType) { + enforceNotSealed(); + mInputType = inputType; + } + + /** + * Gets an optional bundle with extra data. The bundle + * is lazily created and never null. + *

            + * Note: It is recommended to use the package + * name of your application as a prefix for the keys to avoid + * collisions which may confuse an accessibility service if the + * same key has different meaning when emitted from different + * applications. + *

            + * + * @return The bundle. + */ + public Bundle getExtras() { + if (mExtras == null) { + mExtras = new Bundle(); + } + return mExtras; + } + /** * Gets the value of a boolean property. * @@ -1845,6 +2154,45 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeInt(mTextSelectionStart); parcel.writeInt(mTextSelectionEnd); + parcel.writeInt(mInputType); + parcel.writeInt(mLiveRegion); + + if (mExtras != null) { + parcel.writeInt(1); + parcel.writeBundle(mExtras); + } else { + parcel.writeInt(0); + } + + if (mRangeInfo != null) { + parcel.writeInt(1); + parcel.writeInt(mRangeInfo.getType()); + parcel.writeFloat(mRangeInfo.getMin()); + parcel.writeFloat(mRangeInfo.getMax()); + parcel.writeFloat(mRangeInfo.getCurrent()); + } else { + parcel.writeInt(0); + } + + if (mCollectionInfo != null) { + parcel.writeInt(1); + parcel.writeInt(mCollectionInfo.getRowCount()); + parcel.writeInt(mCollectionInfo.getColumnCount()); + parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0); + } else { + parcel.writeInt(0); + } + + if (mCollectionItemInfo != null) { + parcel.writeInt(1); + 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); + } // Since instances of this class are fetched via synchronous i.e. blocking // calls in IPCs we always recycle as soon as the instance is marshaled. @@ -1876,10 +2224,21 @@ public class AccessibilityNodeInfo implements Parcelable { mMovementGranularities = other.mMovementGranularities; final int otherChildIdCount = other.mChildNodeIds.size(); for (int i = 0; i < otherChildIdCount; i++) { - mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i)); + mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i)); } mTextSelectionStart = other.mTextSelectionStart; mTextSelectionEnd = other.mTextSelectionEnd; + mInputType = other.mInputType; + mLiveRegion = other.mLiveRegion; + if (other.mExtras != null && !other.mExtras.isEmpty()) { + getExtras().putAll(other.mExtras); + } + mRangeInfo = (other.mRangeInfo != null) + ? RangeInfo.obtain(other.mRangeInfo) : null; + mCollectionInfo = (other.mCollectionInfo != null) + ? CollectionInfo.obtain(other.mCollectionInfo) : null; + mCollectionItemInfo = (other.mCollectionItemInfo != null) + ? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null; } /** @@ -1927,6 +2286,37 @@ public class AccessibilityNodeInfo implements Parcelable { mTextSelectionStart = parcel.readInt(); mTextSelectionEnd = parcel.readInt(); + + mInputType = parcel.readInt(); + mLiveRegion = parcel.readInt(); + + if (parcel.readInt() == 1) { + getExtras().putAll(parcel.readBundle()); + } + + if (parcel.readInt() == 1) { + mRangeInfo = RangeInfo.obtain( + parcel.readInt(), + parcel.readFloat(), + parcel.readFloat(), + parcel.readFloat()); + } + + if (parcel.readInt() == 1) { + mCollectionInfo = CollectionInfo.obtain( + parcel.readInt(), + parcel.readInt(), + parcel.readInt() == 1); + } + + if (parcel.readInt() == 1) { + mCollectionItemInfo = CollectionItemInfo.obtain( + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt() == 1); + } } /** @@ -1953,6 +2343,23 @@ public class AccessibilityNodeInfo implements Parcelable { mActions = 0; mTextSelectionStart = UNDEFINED; mTextSelectionEnd = UNDEFINED; + mInputType = InputType.TYPE_NULL; + mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE; + if (mExtras != null) { + mExtras.clear(); + } + if (mRangeInfo != null) { + mRangeInfo.recycle(); + mRangeInfo = null; + } + if (mCollectionInfo != null) { + mCollectionInfo.recycle(); + mCollectionInfo = null; + } + if (mCollectionItemInfo != null) { + mCollectionItemInfo.recycle(); + mCollectionItemInfo = null; + } } /** @@ -2131,6 +2538,360 @@ public class AccessibilityNodeInfo implements Parcelable { return builder.toString(); } + /** + * Class with information if a node is a range. Use + * {@link RangeInfo#obtain(int, float, float, float) to get an instance. + */ + public static final class RangeInfo { + private static final int MAX_POOL_SIZE = 10; + + /** Range type: integer. */ + public static final int RANGE_TYPE_INT = 0; + /** Range type: float. */ + public static final int RANGE_TYPE_FLOAT = 1; + /** Range type: percent with values from zero to one.*/ + public static final int RANGE_TYPE_PERCENT = 2; + + private static final SynchronizedPool sPool = + new SynchronizedPool(MAX_POOL_SIZE); + + private int mType; + private float mMin; + private float mMax; + private float mCurrent; + + /** + * Obtains a pooled instance that is a clone of another one. + * + * @param other The instance to clone. + * + * @hide + */ + public static RangeInfo obtain(RangeInfo other) { + return obtain(other.mType, other.mMin, other.mMax, other.mCurrent); + } + + /** + * Obtains a pooled instance. + * + * @param type The type of the range. + * @param min The min value. + * @param max The max value. + * @param current The current value. + */ + public static RangeInfo obtain(int type, float min, float max, float current) { + RangeInfo info = sPool.acquire(); + return (info != null) ? info : new RangeInfo(type, min, max, current); + } + + /** + * Creates a new range. + * + * @param type The type of the range. + * @param min The min value. + * @param max The max value. + * @param current The current value. + */ + private RangeInfo(int type, float min, float max, float current) { + mType = type; + mMin = min; + mMax = max; + mCurrent = current; + } + + /** + * Gets the range type. + * + * @return The range type. + * + * @see #RANGE_TYPE_INT + * @see #RANGE_TYPE_FLOAT + * @see #RANGE_TYPE_PERCENT + */ + public int getType() { + return mType; + } + + /** + * Gets the min value. + * + * @return The min value. + */ + public float getMin() { + return mMin; + } + + /** + * Gets the max value. + * + * @return The max value. + */ + public float getMax() { + return mMax; + } + + /** + * Gets the current value. + * + * @return The current value. + */ + public float getCurrent() { + return mCurrent; + } + + /** + * Recycles this instance. + */ + void recycle() { + clear(); + sPool.release(this); + } + + private void clear() { + mType = 0; + mMin = 0; + mMax = 0; + mCurrent = 0; + } + } + + /** + * Class with information if a node is a collection. Use + * {@link CollectionInfo#obtain(int, int, boolean)} to get an instance. + *

            + * 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. + *

            + */ + public static final class CollectionInfo { + private static final int MAX_POOL_SIZE = 20; + + private static final SynchronizedPool sPool = + new SynchronizedPool(MAX_POOL_SIZE); + + private int mRowCount; + private int mColumnCount; + private boolean mHierarchical; + + /** + * Obtains a pooled instance that is a clone of another one. + * + * @param other The instance to clone. + * + * @hide + */ + public static CollectionInfo obtain(CollectionInfo other) { + return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, + other.mHierarchical); + } + + /** + * Obtains a pooled instance. + * + * @param rowCount The number of rows. + * @param columnCount The number of columns. + * @param hierarchical Whether the collection is hierarchical. + */ + public static CollectionInfo obtain(int rowCount, int columnCount, + boolean hierarchical) { + CollectionInfo info = sPool.acquire(); + return (info != null) ? info : new CollectionInfo(rowCount, + columnCount, hierarchical); + } + + /** + * Creates a new instance. + * + * @param rowCount The number of rows. + * @param columnCount The number of columns. + * @param hierarchical Whether the collection is hierarchical. + */ + private CollectionInfo(int rowCount, int columnCount, + boolean hierarchical) { + mRowCount = rowCount; + mColumnCount = columnCount; + mHierarchical = hierarchical; + } + + /** + * Gets the number of rows. + * + * @return The row count. + */ + public int getRowCount() { + return mRowCount; + } + + /** + * Gets the number of columns. + * + * @return The column count. + */ + public int getColumnCount() { + return mColumnCount; + } + + /** + * Gets if the collection is a hierarchically ordered. + * + * @return Whether the collection is hierarchical. + */ + public boolean isHierarchical() { + return mHierarchical; + } + + /** + * Recycles this instance. + */ + void recycle() { + clear(); + sPool.release(this); + } + + private void clear() { + 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. + *

            + * 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. + *

            + */ + public static final class CollectionItemInfo { + private static final int MAX_POOL_SIZE = 20; + + private static final SynchronizedPool sPool = + new SynchronizedPool(MAX_POOL_SIZE); + + /** + * Obtains a pooled instance that is a clone of another one. + * + * @param other The instance to clone. + * + * @hide + */ + public static CollectionItemInfo obtain(CollectionItemInfo other) { + return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, + other.mColumnIndex, other.mColumnSpan, other.mHeading); + } + + /** + * Obtains a pooled instance. + * + * @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 rowIndex, int rowSpan, + int columnIndex, int columnSpan, boolean heading) { + CollectionItemInfo info = sPool.acquire(); + return (info != null) ? info : new CollectionItemInfo(rowIndex, + rowSpan, columnIndex, columnSpan, heading); + } + + private boolean mHeading; + private int mColumnIndex; + private int mRowIndex; + private int mColumnSpan; + private int mRowSpan; + + /** + * Creates a new instance. + * + * @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 rowIndex, int rowSpan, + int columnIndex, int columnSpan, boolean heading) { + mRowIndex = rowIndex; + mRowSpan = rowSpan; + mColumnIndex = columnIndex; + mColumnSpan = columnSpan; + mHeading = heading; + } + + /** + * Gets the column index at which the item is located. + * + * @return The column index. + */ + public int getColumnIndex() { + return mColumnIndex; + } + + /** + * Gets the row index at which the item is located. + * + * @return The row index. + */ + public int getRowIndex() { + return mRowIndex; + } + + /** + * Gets the number of columns the item spans. + * + * @return The column span. + */ + public int getColumnSpan() { + return mColumnSpan; + } + + /** + * Gets the number of rows the item spans. + * + * @return The row span. + */ + public int getRowSpan() { + return mRowSpan; + } + + /** + * Gets if the collection item is a heading. For example, section + * heading, table header, etc. + * + * @return If the item is a heading. + */ + public boolean isHeading() { + return mHeading; + } + + /** + * Recycles this instance. + */ + void recycle() { + clear(); + sPool.release(this); + } + + private void clear() { + mColumnIndex = 0; + mColumnSpan = 0; + mRowIndex = 0; + mRowSpan = 0; + mHeading = false; + } + } + /** * @see Parcelable.Creator */ diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java index 28518aab8ab95786e29560e3f7cd18eb736ae204..a9473a8df63cb8a992150914c601cf53f975e70d 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java @@ -41,7 +41,7 @@ public class AccessibilityNodeInfoCache { private static final boolean DEBUG = false; - private static final boolean CHECK_INTEGRITY = true; + private static final boolean CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD = true; private final Object mLock = new Object(); @@ -67,16 +67,13 @@ public class AccessibilityNodeInfoCache { if (ENABLED) { final int eventType = event.getEventType(); switch (eventType) { - case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { - // New window so we clear the cache. - mWindowId = event.getWindowId(); - clear(); - } break; + case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: + case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { + // If the active window changes, clear the cache. final int windowId = event.getWindowId(); if (mWindowId != windowId) { - // New window so we clear the cache. mWindowId = windowId; clear(); } @@ -87,34 +84,50 @@ public class AccessibilityNodeInfoCache { case AccessibilityEvent.TYPE_VIEW_SELECTED: case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { - // Since we prefetch the descendants of a node we - // just remove the entire subtree since when the node - // is fetched we will gets its descendant anyway. + refreshCachedNode(event.getSourceNodeId()); + } break; + case AccessibilityEvent.TYPE_VIEW_SCROLLED: { synchronized (mLock) { - final long sourceId = event.getSourceNodeId(); - clearSubTreeLocked(sourceId); - if (eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) { - clearSubtreeWithOldInputFocusLocked(sourceId); - } - if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { - clearSubtreeWithOldAccessibilityFocusLocked(sourceId); - } + clearSubTreeLocked(event.getSourceNodeId()); } } break; - case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: - case AccessibilityEvent.TYPE_VIEW_SCROLLED: { + case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { synchronized (mLock) { - final long accessibilityNodeId = event.getSourceNodeId(); - clearSubTreeLocked(accessibilityNodeId); + final long sourceId = event.getSourceNodeId(); + if ((event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) { + clearSubTreeLocked(sourceId); + } else { + refreshCachedNode(sourceId); + } } } break; } - if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) { + if (CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD && Build.IS_DEBUGGABLE) { checkIntegrity(); } } } + private void refreshCachedNode(long sourceId) { + if (DEBUG) { + Log.i(LOG_TAG, "Refreshing cached node."); + } + synchronized (mLock) { + AccessibilityNodeInfo cachedInfo = mCacheImpl.get(sourceId); + // If the source is not in the cache - nothing to do. + if (cachedInfo == null) { + return; + } + // The node changed so we will just refresh it right now. + if (cachedInfo.refresh(true)) { + return; + } + // Weird, we could not refresh. Just evict the entire sub-tree. + clearSubTreeLocked(sourceId); + } + } + /** * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id. * @@ -212,6 +225,13 @@ public class AccessibilityNodeInfoCache { * @param rootNodeId The root id. */ private void clearSubTreeLocked(long rootNodeId) { + if (DEBUG) { + Log.i(LOG_TAG, "Clearing cached subtree."); + } + clearSubTreeRecursiveLocked(rootNodeId); + } + + private void clearSubTreeRecursiveLocked(long rootNodeId) { AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId); if (current == null) { return; @@ -221,41 +241,7 @@ public class AccessibilityNodeInfoCache { final int childCount = childNodeIds.size(); for (int i = 0; i < childCount; i++) { final long childNodeId = childNodeIds.valueAt(i); - clearSubTreeLocked(childNodeId); - } - } - - /** - * We are enforcing the invariant for a single input focus. - * - * @param currentInputFocusId The current input focused node. - */ - private void clearSubtreeWithOldInputFocusLocked(long currentInputFocusId) { - final int cacheSize = mCacheImpl.size(); - for (int i = 0; i < cacheSize; i++) { - AccessibilityNodeInfo info = mCacheImpl.valueAt(i); - final long infoSourceId = info.getSourceNodeId(); - if (infoSourceId != currentInputFocusId && info.isFocused()) { - clearSubTreeLocked(infoSourceId); - return; - } - } - } - - /** - * We are enforcing the invariant for a single accessibility focus. - * - * @param currentAccessibilityFocusId The current input focused node. - */ - private void clearSubtreeWithOldAccessibilityFocusLocked(long currentAccessibilityFocusId) { - final int cacheSize = mCacheImpl.size(); - for (int i = 0; i < cacheSize; i++) { - AccessibilityNodeInfo info = mCacheImpl.valueAt(i); - final long infoSourceId = info.getSourceNodeId(); - if (infoSourceId != currentAccessibilityFocusId && info.isAccessibilityFocused()) { - clearSubTreeLocked(infoSourceId); - return; - } + clearSubTreeRecursiveLocked(childNodeId); } } @@ -327,12 +313,11 @@ public class AccessibilityNodeInfoCache { } // Check for disconnected nodes or ones from another window. - final int cacheSize = mCacheImpl.size(); - for (int i = 0; i < cacheSize; i++) { + for (int i = 0; i < mCacheImpl.size(); i++) { AccessibilityNodeInfo info = mCacheImpl.valueAt(i); if (!seen.contains(info)) { if (info.getWindowId() == windowId) { - Log.e(LOG_TAG, "Disconneced node: "); + Log.e(LOG_TAG, "Disconneced node: " + info); } else { Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:" + windowId + " " + info); diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java index 688cbdf3c9f7af6db2d6bb12315bedab1def6205..718c32fd8d25b20381849763749d1c939a110dd4 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java +++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java @@ -132,4 +132,19 @@ public abstract class AccessibilityNodeProvider { int virtualViewId) { return null; } + + /** + * Find the virtual view, i.e. a descendant of the host View, that has the + * specified focus type. + * + * @param focus The focus to find. One of + * {@link AccessibilityNodeInfo#FOCUS_INPUT} or + * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}. + * @return The node info of the focused view or null. + * @see AccessibilityNodeInfo#FOCUS_INPUT + * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY + */ + public AccessibilityNodeInfo findFocus(int focus) { + return null; + } } diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 7147c57c8b0d2885e0ea9aaf70c555b582a247f3..3fcd2189066f3fbc6c871c58e6070cc0cb41afd7 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -164,7 +164,7 @@ public class AccessibilityRecord { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId, - mSourceNodeId, GET_SOURCE_PREFETCH_FLAGS); + mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS); } /** diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java new file mode 100644 index 0000000000000000000000000000000000000000..557239f6096dda9c5e967148c2516fe14f70ec77 --- /dev/null +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -0,0 +1,405 @@ +/* + * 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.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 and monitoring preferred video captioning state and visual + * properties. + *

            + * To obtain a handle to the captioning manager, do the following: + *

            + * + *

            CaptioningManager captioningManager =
            + *        (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
            + * + */ +public class CaptioningManager { + /** 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; + + /** Default scaling value for caption fonts. */ + private static final float DEFAULT_FONT_SCALE = 1; + + private final ArrayList + mListeners = new ArrayList(); + 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(); + } + + /** + * @return the user's preferred captioning enabled state + */ + public final boolean isEnabled() { + return Secure.getInt( + mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1; + } + + /** + * @return the raw locale string for the user's preferred captioning + * language + * @hide + */ + public final String getRawLocale() { + return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE); + } + + /** + * @return the locale for the user's preferred captioning language, or null + * if not specified + */ + public final Locale getLocale() { + final String rawLocale = getRawLocale(); + if (!TextUtils.isEmpty(rawLocale)) { + final String[] splitLocale = rawLocale.split("_"); + switch (splitLocale.length) { + case 3: + return new Locale(splitLocale[0], splitLocale[1], splitLocale[2]); + case 2: + return new Locale(splitLocale[0], splitLocale[1]); + case 1: + return new Locale(splitLocale[0]); + } + } + + return null; + } + + /** + * @return the user's preferred font scaling factor for video captions, or 1 if not + * specified + */ + 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 addCaptioningChangeListener(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 #addCaptioningChangeListener}. + * + * @param listener the listener to remove + */ + public void removeCaptioningChangeListener(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; + private static final CaptionStyle YELLOW_ON_BLACK; + private static final CaptionStyle YELLOW_ON_BLUE; + private static final CaptionStyle DEFAULT_CUSTOM; + + /** @hide */ + public static final CaptionStyle[] PRESETS; + + /** @hide */ + public static final int PRESET_CUSTOM = -1; + + /** Edge type value specifying no character edges. */ + public static final int EDGE_TYPE_NONE = 0; + + /** Edge type value specifying uniformly outlined character edges. */ + public static final int EDGE_TYPE_OUTLINE = 1; + + /** Edge type value specifying drop-shadowed character edges. */ + public static final int EDGE_TYPE_DROP_SHADOW = 2; + + /** The preferred foreground color for video captions. */ + public final int foregroundColor; + + /** The preferred background color for video captions. */ + public final int backgroundColor; + + /** + * The preferred edge type for video captions, one of: + *
              + *
            • {@link #EDGE_TYPE_NONE} + *
            • {@link #EDGE_TYPE_OUTLINE} + *
            • {@link #EDGE_TYPE_DROP_SHADOW} + *
            + */ + public final int edgeType; + + /** + * The preferred edge color for video captions, if using an edge type + * other than {@link #EDGE_TYPE_NONE}. + */ + public final int edgeColor; + + /** + * @hide + */ + public final String mRawTypeface; + + private Typeface mParsedTypeface; + + private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor, + String rawTypeface) { + this.foregroundColor = foregroundColor; + this.backgroundColor = backgroundColor; + this.edgeType = edgeType; + this.edgeColor = edgeColor; + + mRawTypeface = rawTypeface; + } + + /** + * @return the preferred {@link Typeface} for video captions, or null if + * not specified + */ + public Typeface getTypeface() { + if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) { + mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL); + } + return mParsedTypeface; + } + + /** + * @hide + */ + public static CaptionStyle getCustomStyle(ContentResolver cr) { + final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM; + final int foregroundColor = Secure.getInt( + 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, defStyle.edgeType); + final int edgeColor = Secure.getInt( + cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor); + + String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE); + if (rawTypeface == null) { + rawTypeface = defStyle.mRawTypeface; + } + + return new CaptionStyle( + foregroundColor, backgroundColor, edgeType, edgeColor, rawTypeface); + } + + static { + WHITE_ON_BLACK = new CaptionStyle( + Color.WHITE, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null); + BLACK_ON_WHITE = new CaptionStyle( + Color.BLACK, Color.WHITE, EDGE_TYPE_NONE, Color.BLACK, null); + YELLOW_ON_BLACK = new CaptionStyle( + Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null); + YELLOW_ON_BLUE = new CaptionStyle( + Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, Color.BLACK, null); + + PRESETS = new CaptionStyle[] { + WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE + }; + + DEFAULT_CUSTOM = WHITE_ON_BLACK; + } + } + + /** + * Listener for changes in captioning properties, including enabled state + * and user style preferences. + */ + public static 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/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java index 14d3d19851f1211d7fe75610e6eaff0207eff19f..71c74506b917895264204aea3c5b84d212800fe3 100644 --- a/core/java/android/view/animation/AnimationSet.java +++ b/core/java/android/view/animation/AnimationSet.java @@ -114,7 +114,7 @@ public class AnimationSet extends Animation { * Constructor to use when building an AnimationSet from code * * @param shareInterpolator Pass true if all of the animations in this set - * should use the interpolator assocciated with this AnimationSet. + * should use the interpolator associated with this AnimationSet. * Pass false if each animation should use its own interpolator. */ public AnimationSet(boolean shareInterpolator) { diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java index e8c1d231dbfbfde691b115e65dc5c479d3a11778..890909bbcaf958d80f26359ec01b8d009a5f9d55 100644 --- a/core/java/android/view/animation/Transformation.java +++ b/core/java/android/view/animation/Transformation.java @@ -29,19 +29,19 @@ public class Transformation { /** * Indicates a transformation that has no effect (alpha = 1 and identity matrix.) */ - public static int TYPE_IDENTITY = 0x0; + public static final int TYPE_IDENTITY = 0x0; /** * Indicates a transformation that applies an alpha only (uses an identity matrix.) */ - public static int TYPE_ALPHA = 0x1; + public static final int TYPE_ALPHA = 0x1; /** * Indicates a transformation that applies a matrix only (alpha = 1.) */ - public static int TYPE_MATRIX = 0x2; + public static final int TYPE_MATRIX = 0x2; /** * Indicates a transformation that applies an alpha and a matrix. */ - public static int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX; + public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX; protected Matrix mMatrix; protected float mAlpha; diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index d6b973e745bed4687b56a80639242b736ddc1c36..f730cf7c451463090488e9d2d447f4a97dc7713d 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -269,8 +269,9 @@ public class BaseInputConnection implements InputConnection { if (content != null) { beginBatchEdit(); removeComposingSpans(content); - endBatchEdit(); + // Note: sendCurrentText does nothing unless mDummyMode is set sendCurrentText(); + endBatchEdit(); } return true; } @@ -467,8 +468,9 @@ public class BaseInputConnection implements InputConnection { content.setSpan(COMPOSING, a, b, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); - endBatchEdit(); + // Note: sendCurrentText does nothing unless mDummyMode is set sendCurrentText(); + endBatchEdit(); } return true; } diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index e7d84c2e6c91efc8aeacd3225bb079f158026109..59330caf0e4185e7b2fffa794b82b23e7f3dff75 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -337,14 +337,17 @@ public interface InputConnection { public boolean deleteSurroundingText(int beforeLength, int afterLength); /** - * Set composing text around the current cursor position with the - * given text, and set the new cursor position. Any composing text - * set previously will be removed automatically. + * Replace the currently composing text with the given text, and + * set the new cursor position. Any composing text set previously + * will be removed automatically. * *

            If there is any composing span currently active, all * characters that it comprises are removed. The passed text is * added in its place, and a composing span is added to this - * text. Finally, the cursor is moved to the location specified by + * text. If there is no composing span active, the passed text is + * added at the cursor position (removing selected characters + * first if any), and a composing span is added on the new text. + * Finally, the cursor is moved to the location specified by * newCursorPosition.

            * *

            This is usually called by IMEs to add or remove or change @@ -447,8 +450,10 @@ public interface InputConnection { * *

            This method removes the contents of the currently composing * text and replaces it with the passed CharSequence, and then - * moves the cursor according to {@code newCursorPosition}. - * This behaves like calling + * moves the cursor according to {@code newCursorPosition}. If there + * is no composing text when this method is called, the new text is + * inserted at the cursor position, removing text inside the selection + * if any. This behaves like calling * {@link #setComposingText(CharSequence, int) setComposingText(text, newCursorPosition)} * then {@link #finishComposingText()}.

            * @@ -461,15 +466,16 @@ public interface InputConnection { * but be careful to wait until the batch edit is over if one is * in progress.

            * - * @param text The committed text. This may include styles. - * @param newCursorPosition The new cursor position around the text. If - * > 0, this is relative to the end of the text - 1; if <= 0, this - * is relative to the start of the text. So a value of 1 will - * always advance you to the position after the full text being - * inserted. Note that this means you can't position the cursor - * within the text, because the editor can make modifications to - * the text you are providing so it is not possible to correctly - * specify locations there. + * @param text The text to commit. This may include styles. + * @param newCursorPosition The new cursor position around the text, + * in Java characters. If > 0, this is relative to the end + * of the text - 1; if <= 0, this is relative to the start + * of the text. So a value of 1 will always advance the cursor + * to the position after the full text being inserted. Note that + * this means you can't position the cursor within the text, + * because the editor can make modifications to the text + * you are providing so it is not possible to correctly specify + * locations there. * @return true on success, false if the input connection is no longer * valid. */ diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 54c2ba5651254d1f7c349b3b1904292f480b45f8..c440c7bd40c2e483ebab25a31739ac8d8ac06361 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -36,6 +36,7 @@ import android.util.AttributeSet; import android.util.Printer; import android.util.Slog; import android.util.Xml; +import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import java.io.IOException; import java.util.ArrayList; @@ -78,13 +79,18 @@ public final class InputMethodInfo implements Parcelable { */ private final ArrayList mSubtypes = new ArrayList(); - private boolean mIsAuxIme; + private final boolean mIsAuxIme; /** - * Cavert: mForceDefault must be false for production. This flag is only for test. + * Caveat: mForceDefault must be false for production. This flag is only for test. */ private final boolean mForceDefault; + /** + * The flag whether this IME supports ways to switch to a next input method (e.g. globe key.) + */ + private final boolean mSupportsSwitchingToNextInputMethod; + /** * Constructor. * @@ -112,7 +118,8 @@ public final class InputMethodInfo implements Parcelable { mService = service; ServiceInfo si = service.serviceInfo; mId = new ComponentName(si.packageName, si.name).flattenToShortString(); - mIsAuxIme = true; + boolean isAuxIme = true; + boolean supportsSwitchingToNextInputMethod = false; // false as default mForceDefault = false; PackageManager pm = context.getPackageManager(); @@ -148,6 +155,9 @@ public final class InputMethodInfo implements Parcelable { com.android.internal.R.styleable.InputMethod_settingsActivity); isDefaultResId = sa.getResourceId( com.android.internal.R.styleable.InputMethod_isDefault, 0); + supportsSwitchingToNextInputMethod = sa.getBoolean( + com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod, + false); sa.recycle(); final int depth = parser.getDepth(); @@ -162,26 +172,28 @@ public final class InputMethodInfo implements Parcelable { } final TypedArray a = res.obtainAttributes( attrs, com.android.internal.R.styleable.InputMethod_Subtype); - InputMethodSubtype subtype = new InputMethodSubtype( - a.getResourceId(com.android.internal.R.styleable - .InputMethod_Subtype_label, 0), - a.getResourceId(com.android.internal.R.styleable - .InputMethod_Subtype_icon, 0), - a.getString(com.android.internal.R.styleable - .InputMethod_Subtype_imeSubtypeLocale), - a.getString(com.android.internal.R.styleable - .InputMethod_Subtype_imeSubtypeMode), - a.getString(com.android.internal.R.styleable - .InputMethod_Subtype_imeSubtypeExtraValue), - a.getBoolean(com.android.internal.R.styleable - .InputMethod_Subtype_isAuxiliary, false), - a.getBoolean(com.android.internal.R.styleable - .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false), - a.getInt(com.android.internal.R.styleable - .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */) - ); + final InputMethodSubtype subtype = new InputMethodSubtypeBuilder() + .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable + .InputMethod_Subtype_label, 0)) + .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable + .InputMethod_Subtype_icon, 0)) + .setSubtypeLocale(a.getString(com.android.internal.R.styleable + .InputMethod_Subtype_imeSubtypeLocale)) + .setSubtypeMode(a.getString(com.android.internal.R.styleable + .InputMethod_Subtype_imeSubtypeMode)) + .setSubtypeExtraValue(a.getString(com.android.internal.R.styleable + .InputMethod_Subtype_imeSubtypeExtraValue)) + .setIsAuxiliary(a.getBoolean(com.android.internal.R.styleable + .InputMethod_Subtype_isAuxiliary, false)) + .setOverridesImplicitlyEnabledSubtype(a.getBoolean( + com.android.internal.R.styleable + .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false)) + .setSubtypeId(a.getInt(com.android.internal.R.styleable + .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)) + .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable + .InputMethod_Subtype_isAsciiCapable, false)).build(); if (!subtype.isAuxiliary()) { - mIsAuxIme = false; + isAuxIme = false; } mSubtypes.add(subtype); } @@ -194,7 +206,7 @@ public final class InputMethodInfo implements Parcelable { } if (mSubtypes.size() == 0) { - mIsAuxIme = false; + isAuxIme = false; } if (additionalSubtypesMap != null && additionalSubtypesMap.containsKey(mId)) { @@ -212,6 +224,8 @@ public final class InputMethodInfo implements Parcelable { } mSettingsActivityName = settingsActivityComponent; mIsDefaultResId = isDefaultResId; + mIsAuxIme = isAuxIme; + mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; } InputMethodInfo(Parcel source) { @@ -219,6 +233,7 @@ public final class InputMethodInfo implements Parcelable { mSettingsActivityName = source.readString(); mIsDefaultResId = source.readInt(); mIsAuxIme = source.readInt() == 1; + mSupportsSwitchingToNextInputMethod = source.readInt() == 1; mService = ResolveInfo.CREATOR.createFromParcel(source); source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR); mForceDefault = false; @@ -250,6 +265,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes.addAll(subtypes); } mForceDefault = forceDefault; + mSupportsSwitchingToNextInputMethod = true; } private static ResolveInfo buildDummyResolveInfo(String packageName, String className, @@ -430,6 +446,14 @@ public final class InputMethodInfo implements Parcelable { return mIsAuxIme; } + /** + * @return true if this input method supports ways to switch to a next input method. + * @hide + */ + public boolean supportsSwitchingToNextInputMethod() { + return mSupportsSwitchingToNextInputMethod; + } + /** * Used to package this object into a {@link Parcel}. * @@ -442,6 +466,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeString(mSettingsActivityName); dest.writeInt(mIsDefaultResId); dest.writeInt(mIsAuxIme ? 1 : 0); + dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); mService.writeToParcel(dest, flags); dest.writeTypedList(mSubtypes); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 4df4734cd98d216e058374e6906862f290cf312a..53f7c796f5e8b01cc6268b957c896f4c2f583643 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -613,7 +613,8 @@ public final class InputMethodManager { public List getEnabledInputMethodSubtypeList(InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { try { - return mService.getEnabledInputMethodSubtypeList(imi, allowsImplicitlySelectedSubtypes); + return mService.getEnabledInputMethodSubtypeList( + imi == null ? null : imi.getId(), allowsImplicitlySelectedSubtypes); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -1411,12 +1412,17 @@ public final class InputMethodManager { try { if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod); - mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd, - selStart, selEnd, candidatesStart, candidatesEnd); + final int oldSelStart = mCursorSelStart; + final int oldSelEnd = mCursorSelEnd; + // Update internal values before sending updateSelection to the IME, because + // if it changes the text within its onUpdateSelection handler in a way that + // does not move the cursor we don't want to call it again with the same values. mCursorSelStart = selStart; mCursorSelEnd = selEnd; mCursorCandStart = candidatesStart; mCursorCandEnd = candidatesEnd; + mCurMethod.updateSelection(oldSelStart, oldSelEnd, + selStart, selEnd, candidatesStart, candidatesEnd); } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } @@ -1874,6 +1880,28 @@ public final class InputMethodManager { } } + /** + * 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. + *

            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. + */ + public boolean shouldOfferSwitchingToNextInputMethod(IBinder imeToken) { + synchronized (mH) { + try { + return mService.shouldOfferSwitchingToNextInputMethod(imeToken); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + return false; + } + } + } + /** * Set additional input method subtypes. Only a process which shares the same uid with the IME * can add additional input method subtypes to the IME. diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index 7895e6f1a3be24291309bbb11d805b2f1b6aa854..88b29776fa0f6a89f04f9dac9fd5019901e26845 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -52,6 +52,7 @@ public final class InputMethodSubtype implements Parcelable { private final boolean mIsAuxiliary; private final boolean mOverridesImplicitlyEnabledSubtype; + private final boolean mIsAsciiCapable; private final int mSubtypeHashCode; private final int mSubtypeIconResId; private final int mSubtypeNameResId; @@ -61,25 +62,146 @@ public final class InputMethodSubtype implements Parcelable { private final String mSubtypeExtraValue; private volatile HashMap mExtraValueHashMapCache; + /** + * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype. + * This class is designed to be used with + * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}. + * The developer needs to be aware of what each parameter means. + */ + public static class InputMethodSubtypeBuilder { + /** + * @param isAuxiliary should true when this subtype is auxiliary, false otherwise. + * An auxiliary subtype has the following differences with a regular subtype: + * - An auxiliary subtype cannot be chosen as the default IME in Settings. + * - The framework will never switch to this subtype through + * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. + * Note that the subtype will still be available in the IME switcher. + * The intent is to allow for IMEs to specify they are meant to be invoked temporarily + * in a one-shot way, and to return to the previous IME once finished (e.g. voice input). + */ + public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) { + mIsAuxiliary = isAuxiliary; + return this; + } + private boolean mIsAuxiliary = false; + + /** + * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be + * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a + * subtype with this parameter set will not be shown in the list of subtypes in each IME's + * subtype enabler. A canonical use of this would be for an IME to supply an "automatic" + * subtype that adapts to the current system language. + */ + public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype( + boolean overridesImplicitlyEnabledSubtype) { + mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; + return this; + } + private boolean mOverridesImplicitlyEnabledSubtype = false; + + /** + * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype + * is ASCII capable, it should guarantee that the user can input ASCII characters with + * this subtype. This is important because many password fields only allow + * ASCII-characters. + */ + public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) { + mIsAsciiCapable = isAsciiCapable; + return this; + } + private boolean mIsAsciiCapable = false; + + /** + * @param subtypeIconResId is a resource ID of the subtype icon drawable. + */ + public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) { + mSubtypeIconResId = subtypeIconResId; + return this; + } + private int mSubtypeIconResId = 0; + + /** + * @param subtypeNameResId is the resource ID of the subtype name string. + * The string resource may have exactly one %s in it. If present, + * the %s part will be replaced with the locale's display name by + * the formatter. Please refer to {@link #getDisplayName} for details. + */ + public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) { + mSubtypeNameResId = subtypeNameResId; + return this; + } + private int mSubtypeNameResId = 0; + + /** + * @param subtypeId is the unique ID for this subtype. The input method framework keeps + * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will + * stay enabled even if other attributes are different. If the ID is unspecified or 0, + * Arrays.hashCode(new Object[] {locale, mode, extraValue, + * isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead. + */ + public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) { + mSubtypeId = subtypeId; + return this; + } + private int mSubtypeId = 0; + + /** + * @param subtypeLocale is the locale supported by this subtype. + */ + public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) { + mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale; + return this; + } + private String mSubtypeLocale = ""; + + /** + * @param subtypeMode is the mode supported by this subtype. + */ + public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) { + mSubtypeMode = subtypeMode == null ? "" : subtypeMode; + return this; + } + private String mSubtypeMode = ""; + /** + * @param subtypeExtraValue is the extra value of the subtype. This string is free-form, + * but the API supplies tools to deal with a key-value comma-separated list; see + * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. + */ + public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) { + mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue; + return this; + } + private String mSubtypeExtraValue = ""; + + /** + * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder. + */ + public InputMethodSubtype build() { + return new InputMethodSubtype(this); + } + } + + private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale, + String mode, String extraValue, boolean isAuxiliary, + boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) { + final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); + builder.mSubtypeNameResId = nameId; + builder.mSubtypeIconResId = iconId; + builder.mSubtypeLocale = locale; + builder.mSubtypeMode = mode; + builder.mSubtypeExtraValue = extraValue; + builder.mIsAuxiliary = isAuxiliary; + builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; + builder.mSubtypeId = id; + builder.mIsAsciiCapable = isAsciiCapable; + return builder; + } + /** * Constructor with no subtype ID specified, overridesImplicitlyEnabledSubtype not specified. - * @param nameId Resource ID of the subtype name string. The string resource may have exactly - * one %s in it. If there is, the %s part will be replaced with the locale's display name by - * the formatter. Please refer to {@link #getDisplayName} for details. - * @param iconId Resource ID of the subtype icon drawable. - * @param locale The locale supported by the subtype - * @param mode The mode supported by the subtype - * @param extraValue The extra value of the subtype. This string is free-form, but the API - * supplies tools to deal with a key-value comma-separated list; see - * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. - * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary - * subtype will not be shown in the list of enabled IMEs for choosing the current IME in - * the Settings even when this subtype is enabled. Please note that this subtype will still - * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch - * to this subtype while an IME is shown. The framework will never switch the current IME to - * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. - * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as - * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). + * Arguments for this constructor have the same meanings as + * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean, + * boolean, int)} except "id" and "overridesImplicitlyEnabledSubtype". * @hide */ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, @@ -89,27 +211,10 @@ public final class InputMethodSubtype implements Parcelable { /** * Constructor with no subtype ID specified. - * @param nameId Resource ID of the subtype name string. The string resource may have exactly - * one %s in it. If there is, the %s part will be replaced with the locale's display name by - * the formatter. Please refer to {@link #getDisplayName} for details. - * @param iconId Resource ID of the subtype icon drawable. - * @param locale The locale supported by the subtype - * @param mode The mode supported by the subtype - * @param extraValue The extra value of the subtype. This string is free-form, but the API - * supplies tools to deal with a key-value comma-separated list; see - * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. - * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary - * subtype will not be shown in the list of enabled IMEs for choosing the current IME in - * the Settings even when this subtype is enabled. Please note that this subtype will still - * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch - * to this subtype while an IME is shown. The framework will never switch the current IME to - * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. - * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as - * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). - * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default - * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this - * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler. - * Having an "automatic" subtype is an example use of this flag. + * @deprecated use {@link InputMethodSubtypeBuilder} instead. + * Arguments for this constructor have the same meanings as + * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean, + * boolean, int)} except "id". */ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) { @@ -119,6 +224,8 @@ public final class InputMethodSubtype implements Parcelable { /** * Constructor. + * @deprecated use {@link InputMethodSubtypeBuilder} instead. + * "isAsciiCapable" is "false" in this constructor. * @param nameId Resource ID of the subtype name string. The string resource may have exactly * one %s in it. If there is, the %s part will be replaced with the locale's display name by * the formatter. Please refer to {@link #getDisplayName} for details. @@ -148,18 +255,29 @@ public final class InputMethodSubtype implements Parcelable { */ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) { - mSubtypeNameResId = nameId; - mSubtypeIconResId = iconId; - mSubtypeLocale = locale != null ? locale : ""; - mSubtypeMode = mode != null ? mode : ""; - mSubtypeExtraValue = extraValue != null ? extraValue : ""; - mIsAuxiliary = isAuxiliary; - mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; + this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary, + overridesImplicitlyEnabledSubtype, id, false)); + } + + /** + * Constructor. + * @param builder Builder for InputMethodSubtype + */ + private InputMethodSubtype(InputMethodSubtypeBuilder builder) { + mSubtypeNameResId = builder.mSubtypeNameResId; + mSubtypeIconResId = builder.mSubtypeIconResId; + mSubtypeLocale = builder.mSubtypeLocale; + mSubtypeMode = builder.mSubtypeMode; + mSubtypeExtraValue = builder.mSubtypeExtraValue; + mIsAuxiliary = builder.mIsAuxiliary; + mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype; + mSubtypeId = builder.mSubtypeId; + mIsAsciiCapable = builder.mIsAsciiCapable; // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype, // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0. - mSubtypeHashCode = id != 0 ? id : hashCodeInternal(mSubtypeLocale, mSubtypeMode, - mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype); - mSubtypeId = id; + mSubtypeHashCode = mSubtypeId != 0 ? mSubtypeId : hashCodeInternal(mSubtypeLocale, + mSubtypeMode, mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, + mIsAsciiCapable); } InputMethodSubtype(Parcel source) { @@ -176,6 +294,7 @@ public final class InputMethodSubtype implements Parcelable { mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1); mSubtypeHashCode = source.readInt(); mSubtypeId = source.readInt(); + mIsAsciiCapable = (source.readInt() == 1); } /** @@ -238,6 +357,15 @@ public final class InputMethodSubtype implements Parcelable { return mOverridesImplicitlyEnabledSubtype; } + /** + * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII + * capable, it should guarantee that the user can input ASCII characters with this subtype. + * This is important because many password fields only allow ASCII-characters. + */ + public boolean isAsciiCapable() { + return mIsAsciiCapable; + } + /** * @param context Context will be used for getting Locale and PackageManager. * @param packageName The package name of the IME @@ -336,7 +464,8 @@ public final class InputMethodSubtype implements Parcelable { && (subtype.getIconResId() == getIconResId()) && (subtype.getLocale().equals(getLocale())) && (subtype.getExtraValue().equals(getExtraValue())) - && (subtype.isAuxiliary() == isAuxiliary()); + && (subtype.isAuxiliary() == isAuxiliary()) + && (subtype.isAsciiCapable() == isAsciiCapable()); } return false; } @@ -357,6 +486,7 @@ public final class InputMethodSubtype implements Parcelable { dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0); dest.writeInt(mSubtypeHashCode); dest.writeInt(mSubtypeId); + dest.writeInt(mIsAsciiCapable ? 1 : 0); } public static final Parcelable.Creator CREATOR @@ -389,9 +519,10 @@ public final class InputMethodSubtype implements Parcelable { } private static int hashCodeInternal(String locale, String mode, String extraValue, - boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) { + boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, + boolean isAsciiCapable) { return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, - overridesImplicitlyEnabledSubtype}); + overridesImplicitlyEnabledSubtype, isAsciiCapable}); } /** diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java deleted file mode 100644 index abc078be271f261a4580e6540c1452a185a0717c..0000000000000000000000000000000000000000 --- a/core/java/android/webkit/AccessibilityInjector.java +++ /dev/null @@ -1,976 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.SystemClock; -import android.provider.Settings; -import android.speech.tts.TextToSpeech; -import android.speech.tts.TextToSpeech.Engine; -import android.speech.tts.TextToSpeech.OnInitListener; -import android.speech.tts.UtteranceProgressListener; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.webkit.WebViewCore.EventHub; - -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Handles injecting accessibility JavaScript and related JavaScript -> Java - * APIs. - */ -class AccessibilityInjector { - private static final String TAG = AccessibilityInjector.class.getSimpleName(); - - private static boolean DEBUG = false; - - // The WebViewClassic this injector is responsible for managing. - private final WebViewClassic mWebViewClassic; - - // Cached reference to mWebViewClassic.getContext(), for convenience. - private final Context mContext; - - // Cached reference to mWebViewClassic.getWebView(), for convenience. - private final WebView mWebView; - - // The Java objects that are exposed to JavaScript. - private TextToSpeechWrapper mTextToSpeech; - private CallbackHandler mCallback; - - // Lazily loaded helper objects. - private AccessibilityManager mAccessibilityManager; - private AccessibilityInjectorFallback mAccessibilityInjectorFallback; - private JSONObject mAccessibilityJSONObject; - - // Whether the accessibility script has been injected into the current page. - private boolean mAccessibilityScriptInjected; - - // Constants for determining script injection strategy. - private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1; - private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0; - @SuppressWarnings("unused") - private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1; - - // Alias for TTS API exposed to JavaScript. - private static final String ALIAS_TTS_JS_INTERFACE = "accessibility"; - - // Alias for traversal callback exposed to JavaScript. - private static final String ALIAS_TRAVERSAL_JS_INTERFACE = "accessibilityTraversal"; - - // Template for JavaScript that injects a screen-reader. - private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE = - "javascript:(function() {" + - " var chooser = document.createElement('script');" + - " chooser.type = 'text/javascript';" + - " chooser.src = '%1s';" + - " document.getElementsByTagName('head')[0].appendChild(chooser);" + - " })();"; - - // Template for JavaScript that performs AndroidVox actions. - private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE = - "(function() {" + - " if ((typeof(cvox) != 'undefined')" + - " && (cvox != null)" + - " && (typeof(cvox.ChromeVox) != 'undefined')" + - " && (cvox.ChromeVox != null)" + - " && (typeof(cvox.AndroidVox) != 'undefined')" + - " && (cvox.AndroidVox != null)" + - " && cvox.ChromeVox.isActive) {" + - " return cvox.AndroidVox.performAction('%1s');" + - " } else {" + - " return false;" + - " }" + - "})()"; - - // JS code used to shut down an active AndroidVox instance. - private static final String TOGGLE_CVOX_TEMPLATE = - "javascript:(function() {" + - " if ((typeof(cvox) != 'undefined')" + - " && (cvox != null)" + - " && (typeof(cvox.ChromeVox) != 'undefined')" + - " && (cvox.ChromeVox != null)" + - " && (typeof(cvox.ChromeVox.host) != 'undefined')" + - " && (cvox.ChromeVox.host != null)) {" + - " cvox.ChromeVox.host.activateOrDeactivateChromeVox(%b);" + - " }" + - "})();"; - - /** - * Creates an instance of the AccessibilityInjector based on - * {@code webViewClassic}. - * - * @param webViewClassic The WebViewClassic that this AccessibilityInjector - * manages. - */ - public AccessibilityInjector(WebViewClassic webViewClassic) { - mWebViewClassic = webViewClassic; - mWebView = webViewClassic.getWebView(); - mContext = webViewClassic.getContext(); - mAccessibilityManager = AccessibilityManager.getInstance(mContext); - } - - /** - * If JavaScript is enabled, pauses or resumes AndroidVox. - * - * @param enabled Whether feedback should be enabled. - */ - public void toggleAccessibilityFeedback(boolean enabled) { - if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { - return; - } - - toggleAndroidVox(enabled); - - if (!enabled && (mTextToSpeech != null)) { - mTextToSpeech.stop(); - } - } - - /** - * Attempts to load scripting interfaces for accessibility. - *

            - * This should only be called before a page loads. - */ - public void addAccessibilityApisIfNecessary() { - if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { - return; - } - - addTtsApis(); - addCallbackApis(); - } - - /** - * Attempts to unload scripting interfaces for accessibility. - *

            - * This should only be called before a page loads. - */ - private void removeAccessibilityApisIfNecessary() { - removeTtsApis(); - removeCallbackApis(); - } - - /** - * Destroys this accessibility injector. - */ - public void destroy() { - if (mTextToSpeech != null) { - mTextToSpeech.shutdown(); - mTextToSpeech = null; - } - - if (mCallback != null) { - mCallback = null; - } - } - - private void toggleAndroidVox(boolean state) { - if (!mAccessibilityScriptInjected) { - return; - } - - final String code = String.format(TOGGLE_CVOX_TEMPLATE, state); - mWebView.loadUrl(code); - } - - /** - * Initializes an {@link AccessibilityNodeInfo} with the actions and - * movement granularity levels supported by this - * {@link AccessibilityInjector}. - *

            - * If an action identifier is added in this method, this - * {@link AccessibilityInjector} should also return {@code true} from - * {@link #supportsAccessibilityAction(int)}. - *

            - * - * @param info The info to initialize. - * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) - */ - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); - info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); - info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); - info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT); - info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT); - info.addAction(AccessibilityNodeInfo.ACTION_CLICK); - info.setClickable(true); - } - - /** - * Returns {@code true} if this {@link AccessibilityInjector} should handle - * the specified action. - * - * @param action An accessibility action identifier. - * @return {@code true} if this {@link AccessibilityInjector} should handle - * the specified action. - */ - public boolean supportsAccessibilityAction(int action) { - switch (action) { - case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: - case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: - case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: - case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: - case AccessibilityNodeInfo.ACTION_CLICK: - return true; - default: - return false; - } - } - - /** - * Performs the specified accessibility action. - * - * @param action The identifier of the action to perform. - * @param arguments The action arguments, or {@code null} if no arguments. - * @return {@code true} if the action was successful. - * @see View#performAccessibilityAction(int, Bundle) - */ - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (!isAccessibilityEnabled()) { - mAccessibilityScriptInjected = false; - toggleFallbackAccessibilityInjector(false); - return false; - } - - if (mAccessibilityScriptInjected) { - return sendActionToAndroidVox(action, arguments); - } - - if (mAccessibilityInjectorFallback != null) { - return mAccessibilityInjectorFallback.performAccessibilityAction(action, arguments); - } - - return false; - } - - /** - * Attempts to handle key events when accessibility is turned on. - * - * @param event The key event to handle. - * @return {@code true} if the event was handled. - */ - public boolean handleKeyEventIfNecessary(KeyEvent event) { - if (!isAccessibilityEnabled()) { - mAccessibilityScriptInjected = false; - toggleFallbackAccessibilityInjector(false); - return false; - } - - if (mAccessibilityScriptInjected) { - // if an accessibility script is injected we delegate to it the key - // handling. this script is a screen reader which is a fully fledged - // solution for blind users to navigate in and interact with web - // pages. - if (event.getAction() == KeyEvent.ACTION_UP) { - mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event); - } else if (event.getAction() == KeyEvent.ACTION_DOWN) { - mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event); - } else { - return false; - } - - return true; - } - - if (mAccessibilityInjectorFallback != null) { - // if an accessibility injector is present (no JavaScript enabled or - // the site opts out injecting our JavaScript screen reader) we let - // it decide whether to act on and consume the event. - return mAccessibilityInjectorFallback.onKeyEvent(event); - } - - return false; - } - - /** - * Attempts to handle selection change events when accessibility is using a - * non-JavaScript method. - *

            - * This must not be called from the main thread. - * - * @param selection The selection string. - * @param token The selection request token. - */ - public void onSelectionStringChangedWebCoreThread(String selection, int token) { - if (mAccessibilityInjectorFallback != null) { - mAccessibilityInjectorFallback.onSelectionStringChangedWebCoreThread(selection, token); - } - } - - /** - * Prepares for injecting accessibility scripts into a new page. - * - * @param url The URL that will be loaded. - */ - public void onPageStarted(String url) { - mAccessibilityScriptInjected = false; - if (DEBUG) { - Log.w(TAG, "[" + mWebView.hashCode() + "] Started loading new page"); - } - addAccessibilityApisIfNecessary(); - } - - /** - * Attempts to inject the accessibility script using a {@code + + + + + + + +

            + + + + + + + + + + + + + + + + + + + + + + + + + +
            + + + + + + + + +
            + +
            + + + + + + + + + + + + +
            + + + + +
            +
            + + + + +
            + public + + + abstract + @interface +

            KeepName

            + + + + + + implements + + Annotation + + + + + +
            + +
            + +
            + + + + + + + + + +
            com.google.android.gms.common.annotation.KeepName
            + + + + + + + +
            + + +

            Class Overview

            +

            Indicates that the name of this object (class, method, etc) should be retained when proguarding. +

            + + + + + +
            + + + + + + + + + + + + + + + + +
            + + +

            Summary

            + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            + [Expand] +
            Inherited Methods
            + +From interface + + java.lang.annotation.Annotation + +
            + + +
            +
            + + +
            + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            + +
            + +
            + + + + + + + + diff --git a/docs/html/reference/com/google/android/gms/common/data/DataBuffer.html b/docs/html/reference/com/google/android/gms/common/data/DataBuffer.html index b45a20592365d4476655563eaae4564720f3e8d7..92f74647359a3ed3a5ce8a42f33bad2eab1506ac 100644 --- a/docs/html/reference/com/google/android/gms/common/data/DataBuffer.html +++ b/docs/html/reference/com/google/android/gms/common/data/DataBuffer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + DataBuffer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
          • Google Services
          • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          +
        • + Overview +
        • Getting Started
        • -
        • - Architectural Overview +
        • + Implementing GCM Client
        • -
        • - Cloud Connection Server +
        • User Notifications
        • -
        • - GCM Client -
        • -
        • - GCM Server -
        • Advanced Topics
        • @@ -685,7 +692,7 @@ Summary: - extends Object
          + extends Object
          @@ -694,7 +701,7 @@ Summary: implements - Iterable<T> + Iterable<T> @@ -710,7 +717,7 @@ Summary: - java.lang.Object + java.lang.Object @@ -970,7 +977,7 @@ Summary: - Iterator<T> + Iterator<T> iterator() @@ -1001,7 +1008,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object clone() @@ -1039,7 +1046,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0) @@ -1068,7 +1075,7 @@ From class final - Class<?> + Class<?> getClass() @@ -1132,7 +1139,7 @@ From class - String + String toString() @@ -1203,7 +1210,7 @@ From class class="jd-expando-trigger-img" /> From interface - java.lang.Iterable + java.lang.Iterable
          Iterator<T> + Iterator<T> iterator() @@ -1453,7 +1460,7 @@ From interface - Iterator<T> + Iterator<T> iterator () diff --git a/docs/html/reference/com/google/android/gms/common/data/DataBufferUtils.html b/docs/html/reference/com/google/android/gms/common/data/DataBufferUtils.html index a865899a525bd873c0f65fb723c252a0d91394d9..75d0c6b1af9cc5565779bd140200b2fbbc67010a 100644 --- a/docs/html/reference/com/google/android/gms/common/data/DataBufferUtils.html +++ b/docs/html/reference/com/google/android/gms/common/data/DataBufferUtils.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + DataBufferUtils | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -691,7 +698,7 @@ Summary: - extends Object
        + extends Object
        @@ -716,7 +723,7 @@ Summary: - java.lang.Object + java.lang.Object @@ -807,42 +814,42 @@ Summary: - String + String EXTRA_EXCLUSIVE_BIT_MASK Used to bundle the exclusive bit mask of the player for auto-match criteria. - String + String EXTRA_INVITATION Used to return an Invitation. - String + String EXTRA_MAX_AUTOMATCH_PLAYERS Used to return the maximum number of players that should be added to a room by auto-matching. - String + String EXTRA_MIN_AUTOMATCH_PLAYERS Used to return the minimum number of players that should be added to a room by auto-matching. - String + String EXTRA_PLAYERS Used to return a list of player IDs. - String + String EXTRA_ROOM Used to return a Room. @@ -1152,7 +1159,7 @@ Summary: void - declineRoomInvitation(String invitationId) + declineRoomInvitation(String invitationId)
        Decline an invitation for a real-time room.
        @@ -1188,7 +1195,7 @@ Summary: void - dismissRoomInvitation(String invitationId) + dismissRoomInvitation(String invitationId)
        Dismiss an invitation to a real-time room.
        @@ -1203,7 +1210,7 @@ Summary: - Intent + Intent getAchievementsIntent() @@ -1221,7 +1228,7 @@ Summary: - Intent + Intent getAllLeaderboardsIntent() @@ -1239,7 +1246,7 @@ Summary: - String + String getAppId() @@ -1257,7 +1264,7 @@ Summary: - String + String getCurrentAccountName() @@ -1307,7 +1314,7 @@ Summary: - String + String getCurrentPlayerId() @@ -1323,7 +1330,7 @@ Summary: - Intent + Intent getInvitationInboxIntent() @@ -1341,10 +1348,10 @@ Summary: - Intent + Intent - getLeaderboardIntent(String leaderboardId) + getLeaderboardIntent(String leaderboardId)
        Gets an intent to show a leaderboard for a game.
        @@ -1362,7 +1369,7 @@ Summary: RealTimeSocket - getRealTimeSocketForParticipant(String roomId, String participantId) + getRealTimeSocketForParticipant(String roomId, String participantId)
        Returns a RealTimeSocket for carrying network traffic to the given peer.
        @@ -1377,7 +1384,7 @@ Summary: - Intent + Intent getRealTimeWaitingRoomIntent(Room room, int minParticipantsToStart) @@ -1396,7 +1403,7 @@ Summary: - Intent + Intent getSelectPlayersIntent(int minPlayers, int maxPlayers) @@ -1414,7 +1421,7 @@ Summary: - Intent + Intent getSettingsIntent() @@ -1436,7 +1443,7 @@ Summary: void - incrementAchievement(String id, int numSteps) + incrementAchievement(String id, int numSteps)
        Increments an achievement by the given number of steps.
        @@ -1454,7 +1461,7 @@ Summary: void - incrementAchievementImmediate(OnAchievementUpdatedListener listener, String id, int numSteps) + incrementAchievementImmediate(OnAchievementUpdatedListener listener, String id, int numSteps)
        Increments an achievement by the given number of steps.
        @@ -1565,7 +1572,7 @@ Summary: void - leaveRoom(RoomUpdateListener listener, String roomId) + leaveRoom(RoomUpdateListener listener, String roomId)
        Leave the specified room.
        @@ -1583,7 +1590,7 @@ Summary: void - loadAchievements(OnAchievementsLoadedListener listener) + loadAchievements(OnAchievementsLoadedListener listener, boolean forceReload)
        Asynchronously load achievement data for the currently signed in player.
        @@ -1601,9 +1608,9 @@ Summary: void - loadAchievements(OnAchievementsLoadedListener listener, boolean forceReload) + loadGame(OnGamesLoadedListener listener) -
        Asynchronously load achievement data for the currently signed in player.
        +
        Load the details for the current game.
        @@ -1619,9 +1626,10 @@ Summary: void - loadGame(OnGamesLoadedListener listener) + loadInvitablePlayers(OnPlayersLoadedListener listener, int pageSize, boolean forceReload) -
        Load the details for the current game.
        +
        Load the initial page of players the currently signed-in player can invite to a multiplayer + game, sorted alphabetically by name.
        @@ -1637,10 +1645,9 @@ Summary: void - loadInvitablePlayers(OnPlayersLoadedListener listener, int pageSize, boolean forceReload) + loadInvitations(OnInvitationsLoadedListener listener) -
        Load the initial page of players the currently signed-in player can invite to a multiplayer - game, sorted alphabetically by name.
        +
        Asynchronously load the list of invitations for the current game.
        @@ -1656,9 +1663,9 @@ Summary: void - loadInvitations(OnInvitationsLoadedListener listener) + loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener listener, boolean forceReload) -
        Asynchronously load the list of invitations for the current game.
        +
        Asynchronously load the list of leaderboard metadata for this game.
        @@ -1674,9 +1681,14 @@ Summary: void - loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener listener, String leaderboardId) + loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener listener, String leaderboardId) -
        Asynchronously load a specific leaderboard's metadata for this game.
        +
        + This method is deprecated. + This form of the API is deprecated and will be removed in a future release. + Please use loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener, String, boolean) + instead. +
        @@ -1694,7 +1706,11 @@ Summary: loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener listener) -
        Asynchronously load the list of leaderboard metadata for this game.
        +
        + This method is deprecated. + This form of the API is deprecated and will be removed in a future release. + Please use loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener, boolean) instead. +
        @@ -1710,9 +1726,9 @@ Summary: void - loadMoreInvitablePlayers(OnPlayersLoadedListener listener, int pageSize) + loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener listener, String leaderboardId, boolean forceReload) -
        Asynchronously loads an additional page of invitable players.
        +
        Asynchronously load a specific leaderboard's metadata for this game.
        @@ -1728,9 +1744,9 @@ Summary: void - loadMoreScores(OnLeaderboardScoresLoadedListener listener, LeaderboardScoreBuffer buffer, int maxResults, int pageDirection) + loadMoreInvitablePlayers(OnPlayersLoadedListener listener, int pageSize) -
        Asynchronously loads an additional page of score data for the given score buffer.
        +
        Asynchronously loads an additional page of invitable players.
        @@ -1746,9 +1762,9 @@ Summary: void - loadPlayer(OnPlayersLoadedListener listener, String playerId) + loadMoreScores(OnLeaderboardScoresLoadedListener listener, LeaderboardScoreBuffer buffer, int maxResults, int pageDirection) -
        Asynchronously loads the profile for the requested player ID.
        +
        Asynchronously loads an additional page of score data for the given score buffer.
        @@ -1764,9 +1780,9 @@ Summary: void - loadPlayerCenteredScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) + loadPlayer(OnPlayersLoadedListener listener, String playerId) -
        Asynchronously load the player-centered page of scores for a given leaderboard.
        +
        Asynchronously loads the profile for the requested player ID.
        @@ -1782,7 +1798,7 @@ Summary: void - loadPlayerCenteredScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults) + loadPlayerCenteredScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload)
        Asynchronously load the player-centered page of scores for a given leaderboard.
        @@ -1800,9 +1816,9 @@ Summary: void - loadTopScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults) + loadPlayerCenteredScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults) -
        Asynchronously load the top page of scores for a given leaderboard.
        +
        Asynchronously load the player-centered page of scores for a given leaderboard.
        @@ -1818,7 +1834,7 @@ Summary: void - loadTopScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) + loadTopScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults)
        Asynchronously load the top page of scores for a given leaderboard.
        @@ -1833,6 +1849,24 @@ Summary: + void + + + loadTopScores(OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) + +
        Asynchronously load the top page of scores for a given leaderboard.
        + + + + + + + + + + + + void @@ -1844,7 +1878,7 @@ Summary: - + @@ -1862,7 +1896,7 @@ Summary: - + @@ -1881,7 +1915,7 @@ Summary: - + @@ -1899,7 +1933,7 @@ Summary: - + @@ -1909,7 +1943,7 @@ Summary: void - revealAchievement(String id) + revealAchievement(String id)
        Reveal a hidden achievement to the currently signed in player.
        @@ -1917,7 +1951,7 @@ Summary: - + @@ -1927,7 +1961,7 @@ Summary: void - revealAchievementImmediate(OnAchievementUpdatedListener listener, String id) + revealAchievementImmediate(OnAchievementUpdatedListener listener, String id)
        Reveal a hidden achievement to the currently signed in player.
        @@ -1935,7 +1969,7 @@ Summary: - + @@ -1945,7 +1979,7 @@ Summary: int - sendReliableRealTimeMessage(RealTimeReliableMessageSentListener listener, byte[] messageData, String roomId, String recipientParticipantId) + sendReliableRealTimeMessage(RealTimeReliableMessageSentListener listener, byte[] messageData, String roomId, String recipientParticipantId)
        Send a message to a participant in a real-time room reliably.
        @@ -1953,7 +1987,7 @@ Summary: - + @@ -1963,7 +1997,7 @@ Summary: int - sendUnreliableRealTimeMessage(byte[] messageData, String roomId, List<String> recipientParticipantIds) + sendUnreliableRealTimeMessage(byte[] messageData, String roomId, List<String> recipientParticipantIds)
        Send a message to one or more participants in a real-time room.
        @@ -1971,7 +2005,7 @@ Summary: - + @@ -1981,7 +2015,7 @@ Summary: int - sendUnreliableRealTimeMessage(byte[] messageData, String roomId, String recipientParticipantId) + sendUnreliableRealTimeMessage(byte[] messageData, String roomId, String recipientParticipantId)
        Send a message to a participant in a real-time room.
        @@ -1989,7 +2023,7 @@ Summary: - + @@ -1999,7 +2033,7 @@ Summary: int - sendUnreliableRealTimeMessageToAll(byte[] messageData, String roomId) + sendUnreliableRealTimeMessageToAll(byte[] messageData, String roomId)
        Send a message to all participants in a real-time room.
        @@ -2007,7 +2041,7 @@ Summary: - + @@ -2026,7 +2060,7 @@ Summary: - + @@ -2045,7 +2079,7 @@ Summary: - + @@ -2055,7 +2089,7 @@ Summary: void - setViewForPopups(View gamesContentView) + setViewForPopups(View gamesContentView)
        Sets the View to use as a content view for popups.
        @@ -2063,7 +2097,7 @@ Summary: - + @@ -2081,7 +2115,7 @@ Summary: - + @@ -2099,7 +2133,7 @@ Summary: - + @@ -2109,7 +2143,7 @@ Summary: void - submitScore(String leaderboardId, long score) + submitScore(String leaderboardId, long score)
        Submit a score to a leaderboard for the currently signed in player.
        @@ -2117,7 +2151,7 @@ Summary: - + @@ -2127,7 +2161,7 @@ Summary: void - submitScoreImmediate(OnScoreSubmittedListener listener, String leaderboardId, long score) + submitScoreImmediate(OnScoreSubmittedListener listener, String leaderboardId, long score)
        Submit a score to a leaderboard for the currently signed in player.
        @@ -2135,7 +2169,7 @@ Summary: - + @@ -2145,7 +2179,7 @@ Summary: void - unlockAchievement(String id) + unlockAchievement(String id)
        Unlock an achievement for the currently signed in player.
        @@ -2153,7 +2187,7 @@ Summary: - + @@ -2163,7 +2197,7 @@ Summary: void - unlockAchievementImmediate(OnAchievementUpdatedListener listener, String id) + unlockAchievementImmediate(OnAchievementUpdatedListener listener, String id)
        Unlock an achievement for the currently signed in player.
        @@ -2171,7 +2205,7 @@ Summary: - + @@ -2189,7 +2223,7 @@ Summary: - + @@ -2207,7 +2241,7 @@ Summary: - + @@ -2247,7 +2281,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object clone() @@ -2285,7 +2319,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0) @@ -2314,7 +2348,7 @@ From class final - Class<?> + Class<?> getClass() @@ -2378,7 +2412,7 @@ From class - String + String toString() @@ -2687,7 +2721,7 @@ From interface public static final - String + String EXTRA_EXCLUSIVE_BIT_MASK @@ -2725,7 +2759,7 @@ From interface public static final - String + String EXTRA_INVITATION @@ -2764,7 +2798,7 @@ From interface public static final - String + String EXTRA_MAX_AUTOMATCH_PLAYERS @@ -2807,7 +2841,7 @@ From interface public static final - String + String EXTRA_MIN_AUTOMATCH_PLAYERS @@ -2850,7 +2884,7 @@ From interface public static final - String + String EXTRA_PLAYERS @@ -2893,7 +2927,7 @@ From interface public static final - String + String EXTRA_ROOM @@ -4152,7 +4186,7 @@ From interface void declineRoomInvitation - (String invitationId) + (String invitationId)
        @@ -4221,7 +4255,7 @@ From interface void dismissRoomInvitation - (String invitationId) + (String invitationId)
        @@ -4258,7 +4292,7 @@ From interface - Intent + Intent getAchievementsIntent () @@ -4299,7 +4333,7 @@ From interface - Intent + Intent getAllLeaderboardsIntent () @@ -4339,7 +4373,7 @@ From interface - String + String getAppId () @@ -4373,7 +4407,7 @@ From interface - String + String getCurrentAccountName () @@ -4394,13 +4428,14 @@ From interface your manifest in order to use this method.

        Returns
        -
        • Account name for the currently selected account.
        +
        • Account name for the currently selected account. May be null if an error occurred + while communicating with the games service.
        Throws
        - @@ -4473,7 +4508,8 @@ From interface

        Returns
        -
        • Player representing the currently signed in player. +
          • Player representing the currently signed in player. May be null if an error + occurred while communicating with the games service.
        @@ -4491,7 +4527,7 @@ From interface - String + String getCurrentPlayerId() @@ -4507,7 +4543,8 @@ From interface

        Returns
        -
        • The player ID for the currently signed in player. +
          • The player ID for the currently signed in player. May be null if an error occurred + while communicating with the games service.
        @@ -4525,7 +4562,7 @@ From interface - Intent + Intent getInvitationInboxIntent() @@ -4566,10 +4603,10 @@ From interface - Intent + Intent getLeaderboardIntent - (String leaderboardId) + (String leaderboardId)
        @@ -4618,7 +4655,7 @@ From interface RealTimeSocket getRealTimeSocketForParticipant - (String roomId, String participantId) + (String roomId, String participantId)
        @@ -4666,7 +4703,7 @@ From interface - Intent + Intent getRealTimeWaitingRoomIntent (Room room, int minParticipantsToStart) @@ -4753,7 +4790,7 @@ From interface - Intent + Intent getSelectPlayersIntent (int minPlayers, int maxPlayers) @@ -4817,7 +4854,7 @@ From interface - Intent + Intent getSettingsIntent () @@ -4865,7 +4902,7 @@ From interface void incrementAchievement - (String id, int numSteps) + (String id, int numSteps)
        @@ -4917,7 +4954,7 @@ From interface void incrementAchievementImmediate - (OnAchievementUpdatedListener listener, String id, int numSteps) + (OnAchievementUpdatedListener listener, String id, int numSteps)
        @@ -5176,7 +5213,7 @@ From interface void leaveRoom - (RoomUpdateListener listener, String roomId) + (RoomUpdateListener listener, String roomId)
        @@ -5212,7 +5249,7 @@ From interface
        - +

        @@ -5225,7 +5262,7 @@ From interface void loadAchievements - (OnAchievementsLoadedListener listener) + (OnAchievementsLoadedListener listener, boolean forceReload)

        @@ -5238,10 +5275,54 @@ From interface

        Asynchronously load achievement data for the currently signed in player.

        The result is delivered to the given listener on the main thread. If disconnect() is - called before the result is ready it will not be delivered. -

        - This form of the API is deprecated and will be removed in a future release. Please use - loadAchievements(OnAchievementsLoadedListener, boolean) instead.

        + called before the result is ready it will not be delivered.

        +
        +
        Parameters
        +
        SecurityException + SecurityException If your app doesn't have the GET_ACCOUNTS permission.
        + + + + + + +
        listener + The listener that is called when the load is complete. The listener is called + on the main thread.
        forceReload + If true, this call will clear any locally cached data and attempt to fetch + the latest data from the server. This would commonly be used for something like a + user-initiated refresh. Normally, this should be set to false to gain advantages + of data caching. +
        +
        + +
        +
        + + + + +
        +

        + + public + + + + + void + + loadGame + (OnGamesLoadedListener listener) +

        +
        +
        + + + +
        +
        + +

        Load the details for the current game.

        Parameters
        @@ -5258,7 +5339,7 @@ From interface - +

        @@ -5270,8 +5351,8 @@ From interface void - loadAchievements - (OnAchievementsLoadedListener listener, boolean forceReload) + loadInvitablePlayers + (OnPlayersLoadedListener listener, int pageSize, boolean forceReload)

        @@ -5281,7 +5362,8 @@ From interface
        -

        Asynchronously load achievement data for the currently signed in player. +

        Load the initial page of players the currently signed-in player can invite to a multiplayer + game, sorted alphabetically by name.

        The result is delivered to the given listener on the main thread. If disconnect() is called before the result is ready it will not be delivered.

        @@ -5293,6 +5375,13 @@ From interface
        + + + - - -
        The listener that is called when the load is complete. The listener is called on the main thread.
        pageSize + The number of entries to request for this initial page. Note that if cached + data already exists, the returned buffer may contain more than this size, but it + is guaranteed to contain at least this many if the collection contains enough + records. This must be a value between 1 and 25.
        forceReload If true, this call will clear any locally cached data and attempt to fetch @@ -5308,7 +5397,7 @@ From interface - +

        @@ -5320,8 +5409,8 @@ From interface void - loadGame - (OnGamesLoadedListener listener) + loadInvitations + (OnInvitationsLoadedListener listener)

        @@ -5331,7 +5420,10 @@ From interface
        -

        Load the details for the current game.

        +

        Asynchronously load the list of invitations for the current game. +

        + The result is delivered to the given listener on the main thread. If disconnect() is + called before the result is ready it will not be delivered.

        Parameters
        @@ -5348,7 +5440,7 @@ From interface - +

        @@ -5360,8 +5452,8 @@ From interface void - loadInvitablePlayers - (OnPlayersLoadedListener listener, int pageSize, boolean forceReload) + loadLeaderboardMetadata + (OnLeaderboardMetadataLoadedListener listener, boolean forceReload)

        @@ -5371,8 +5463,7 @@ From interface
        -

        Load the initial page of players the currently signed-in player can invite to a multiplayer - game, sorted alphabetically by name. +

        Asynchronously load the list of leaderboard metadata for this game.

        The result is delivered to the given listener on the main thread. If disconnect() is called before the result is ready it will not be delivered.

        @@ -5384,13 +5475,6 @@ From interface
        - - - + on the main thread. + + +
        The listener that is called when the load is complete. The listener is called on the main thread.
        pageSize - The number of entries to request for this initial page. Note that if cached - data already exists, the returned buffer may contain more than this size, but it - is guaranteed to contain at least this many if the collection contains enough - records. This must be a value between 1 and 25.
        forceReload If true, this call will clear any locally cached data and attempt to fetch @@ -5406,7 +5490,7 @@ From interface - +

        @@ -5418,8 +5502,8 @@ From interface void - loadInvitations - (OnInvitationsLoadedListener listener) + loadLeaderboardMetadata + (OnLeaderboardMetadataLoadedListener listener, String leaderboardId)

        @@ -5428,8 +5512,15 @@ From interface
        - -

        Asynchronously load the list of invitations for the current game. +

        +

        + This method is deprecated.
        + This form of the API is deprecated and will be removed in a future release. + Please use loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener, String, boolean) + instead. + +

        +

        Asynchronously load a specific leaderboard's metadata for this game.

        The result is delivered to the given listener on the main thread. If disconnect() is called before the result is ready it will not be delivered.

        @@ -5439,8 +5530,11 @@ From interface
        listener The listener that is called when the load is complete. The listener is called - on the main thread. -
        leaderboardId + ID of the leaderboard to load metadata for.
        @@ -5449,7 +5543,7 @@ From interface
        - +

        @@ -5462,7 +5556,7 @@ From interface void loadLeaderboardMetadata - (OnLeaderboardMetadataLoadedListener listener, String leaderboardId) + (OnLeaderboardMetadataLoadedListener listener)

        @@ -5471,8 +5565,14 @@ From interface
        - -

        Asynchronously load a specific leaderboard's metadata for this game. +

        +

        + This method is deprecated.
        + This form of the API is deprecated and will be removed in a future release. + Please use loadLeaderboardMetadata(OnLeaderboardMetadataLoadedListener, boolean) instead. + +

        +

        Asynchronously load the list of leaderboard metadata for this game.

        The result is delivered to the given listener on the main thread. If disconnect() is called before the result is ready it will not be delivered.

        @@ -5484,11 +5584,6 @@ From interface
        The listener that is called when the load is complete. The listener is called on the main thread.
        leaderboardId - ID of the leaderboard to load metadata for. -
        @@ -5496,7 +5591,7 @@ From interface
        - +

        @@ -5509,7 +5604,7 @@ From interface void loadLeaderboardMetadata - (OnLeaderboardMetadataLoadedListener listener) + (OnLeaderboardMetadataLoadedListener listener, String leaderboardId, boolean forceReload)

        @@ -5519,7 +5614,7 @@ From interface
        -

        Asynchronously load the list of leaderboard metadata for this game. +

        Asynchronously load a specific leaderboard's metadata for this game.

        The result is delivered to the given listener on the main thread. If disconnect() is called before the result is ready it will not be delivered.

        @@ -5529,7 +5624,18 @@ From interface listener The listener that is called when the load is complete. The listener is called - on the main thread. + on the main thread. + + + leaderboardId + ID of the leaderboard to load metadata for. + + + forceReload + If true, this call will clear any locally cached data and attempt to fetch + the latest data from the server. This would commonly be used for something like a + user-initiated refresh. Normally, this should be set to false to gain advantages + of data caching. @@ -5658,7 +5764,7 @@ From interface void loadPlayer - (OnPlayersLoadedListener listener, String playerId) + (OnPlayersLoadedListener listener, String playerId)
        @@ -5705,7 +5811,7 @@ From interface void loadPlayerCenteredScores - (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) + (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload)
        @@ -5777,7 +5883,7 @@ From interface void loadPlayerCenteredScores - (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults) + (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults)
        @@ -5842,7 +5948,7 @@ From interface void loadTopScores - (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults) + (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults)
        @@ -5906,7 +6012,7 @@ From interface void loadTopScores - (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) + (OnLeaderboardScoresLoadedListener listener, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload)
        @@ -6168,7 +6274,7 @@ From interface void revealAchievement - (String id) + (String id)
        @@ -6219,7 +6325,7 @@ From interface void revealAchievementImmediate - (OnAchievementUpdatedListener listener, String id) + (OnAchievementUpdatedListener listener, String id)
        @@ -6275,7 +6381,7 @@ From interface int sendReliableRealTimeMessage - (RealTimeReliableMessageSentListener listener, byte[] messageData, String roomId, String recipientParticipantId) + (RealTimeReliableMessageSentListener listener, byte[] messageData, String roomId, String recipientParticipantId)
        @@ -6337,7 +6443,7 @@ From interface int sendUnreliableRealTimeMessage - (byte[] messageData, String roomId, List<String> recipientParticipantIds) + (byte[] messageData, String roomId, List<String> recipientParticipantIds)
        @@ -6394,7 +6500,7 @@ From interface int sendUnreliableRealTimeMessage - (byte[] messageData, String roomId, String recipientParticipantId) + (byte[] messageData, String roomId, String recipientParticipantId)
        @@ -6451,7 +6557,7 @@ From interface int sendUnreliableRealTimeMessageToAll - (byte[] messageData, String roomId) + (byte[] messageData, String roomId)
        @@ -6584,7 +6690,7 @@ From interface void setViewForPopups - (View gamesContentView) + (View gamesContentView)
        @@ -6696,7 +6802,7 @@ From interface void submitScore - (String leaderboardId, long score) + (String leaderboardId, long score)
        @@ -6761,7 +6867,7 @@ From interface void submitScoreImmediate - (OnScoreSubmittedListener listener, String leaderboardId, long score) + (OnScoreSubmittedListener listener, String leaderboardId, long score)
        @@ -6837,7 +6943,7 @@ From interface void unlockAchievement - (String id) + (String id)
        @@ -6888,7 +6994,7 @@ From interface void unlockAchievementImmediate - (OnAchievementUpdatedListener listener, String id) + (OnAchievementUpdatedListener listener, String id)
        diff --git a/docs/html/reference/com/google/android/gms/games/OnGamesLoadedListener.html b/docs/html/reference/com/google/android/gms/games/OnGamesLoadedListener.html index e715f6cb6847b5d0f21df1cacc12c7461d6c0f2e..d92b0eb0cc9b28aec6f4d5c7c27bfa216102d51c 100644 --- a/docs/html/reference/com/google/android/gms/games/OnGamesLoadedListener.html +++ b/docs/html/reference/com/google/android/gms/games/OnGamesLoadedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + OnGamesLoadedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -679,9 +686,6 @@ Summary: - | Protected Methods - - | Inherited Methods @@ -710,7 +714,7 @@ Summary: - extends Object
        + extends Object
        @@ -719,7 +723,7 @@ Summary: implements - Parcelable + Parcelable Player @@ -737,7 +741,7 @@ Summary: - java.lang.Object + java.lang.Object @@ -872,7 +876,7 @@ android.os.Parcelable public static final - PlayerEntityCreator + Creator<PlayerEntity> CREATOR @@ -923,7 +927,7 @@ android.os.Parcelable boolean - equals(Object obj) + equals(Object obj) @@ -954,7 +958,7 @@ android.os.Parcelable - String + String getDisplayName() @@ -975,7 +979,7 @@ android.os.Parcelable void - getDisplayName(CharArrayBuffer dataOut) + getDisplayName(CharArrayBuffer dataOut)
        Loads the player's display name into the given CharArrayBuffer.
        @@ -990,7 +994,7 @@ android.os.Parcelable - Uri + Uri getHiResImageUri() @@ -1008,7 +1012,7 @@ android.os.Parcelable - Uri + Uri getIconImageUri() @@ -1026,7 +1030,7 @@ android.os.Parcelable - String + String getPlayerId() @@ -1132,7 +1136,7 @@ android.os.Parcelable - String + String toString() @@ -1151,7 +1155,7 @@ android.os.Parcelable void - writeToParcel(Parcel dest, int flags) + writeToParcel(Parcel dest, int flags) @@ -1162,63 +1166,6 @@ android.os.Parcelable - - - - - - - - - - - - - - - - - - - - - - - - -
        Protected Methods
        - - - - static - - ClassLoader - - getUnparcelClassLoader() - -
        - - - - static - - Integer - - getUnparcelClientVersion() - -
        - - - - - - boolean - - shouldDowngrade() - -
        - - @@ -1236,7 +1183,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object clone() @@ -1274,7 +1221,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0) @@ -1303,7 +1250,7 @@ From class final - Class<?> + Class<?> getClass() @@ -1367,7 +1314,7 @@ From class - String + String toString() @@ -1438,7 +1385,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1) @@ -1577,7 +1524,7 @@ From interface - String + String getDisplayName() @@ -1598,7 +1545,7 @@ From interface void - getDisplayName(CharArrayBuffer dataOut) + getDisplayName(CharArrayBuffer dataOut)
        Loads the player's display name into the given CharArrayBuffer.
        @@ -1613,7 +1560,7 @@ From interface - Uri + Uri getHiResImageUri() @@ -1631,7 +1578,7 @@ From interface - Uri + Uri getIconImageUri() @@ -1649,7 +1596,7 @@ From interface - String + String getPlayerId() @@ -1759,7 +1706,7 @@ From interface public static final - PlayerEntityCreator + Creator<PlayerEntity> CREATOR @@ -1838,7 +1785,7 @@ From interface boolean equals - (Object obj) + (Object obj)
        @@ -1911,7 +1858,7 @@ From interface - String + String getDisplayName () @@ -1948,7 +1895,7 @@ From interface void getDisplayName - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1984,7 +1931,7 @@ From interface - Uri + Uri getHiResImageUri () @@ -2021,7 +1968,7 @@ From interface - Uri + Uri getIconImageUri () @@ -2059,7 +2006,7 @@ From interface - String + String getPlayerId () @@ -2260,7 +2207,7 @@ From interface - String + String toString () @@ -2292,7 +2239,7 @@ From interface void writeToParcel - (Parcel dest, int flags) + (Parcel dest, int flags)
        @@ -2313,113 +2260,6 @@ From interface -

        Protected Methods

        - - - - - -
        -

        - - protected - static - - - - ClassLoader - - getUnparcelClassLoader - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The ClassLoader to use for unparceling, if any. -
        -
        - -
        -
        - - - - -
        -

        - - protected - static - - - - Integer - - getUnparcelClientVersion - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The client version to use for unparceling, if known. -
        -
        - -
        -
        - - - - -
        -

        - - protected - - - - - boolean - - shouldDowngrade - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • Whether or not this object has been downgraded to hand back to a client. -
        -
        - -
        -
        - - - diff --git a/docs/html/reference/com/google/android/gms/games/RealTimeSocket.html b/docs/html/reference/com/google/android/gms/games/RealTimeSocket.html index ca4ce65bd3b5d0b7ad72665cbe894dc7f5011b50..e0f0b9b14474b4337bc28f2ebc570e5b51901e3c 100644 --- a/docs/html/reference/com/google/android/gms/games/RealTimeSocket.html +++ b/docs/html/reference/com/google/android/gms/games/RealTimeSocket.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + RealTimeSocket | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -779,7 +786,7 @@ onkeyup="return search_changed(event, false, '/')" /> - InputStream + InputStream getInputStream() @@ -797,7 +804,7 @@ onkeyup="return search_changed(event, false, '/')" /> - OutputStream + OutputStream getOutputStream() @@ -815,7 +822,7 @@ onkeyup="return search_changed(event, false, '/')" /> - ParcelFileDescriptor + ParcelFileDescriptor getParcelFileDescriptor() @@ -919,7 +926,7 @@ onkeyup="return search_changed(event, false, '/')" />
        Throws
        - @@ -940,7 +947,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - InputStream + InputStream getInputStream() @@ -963,7 +970,7 @@ onkeyup="return search_changed(event, false, '/')" />
        Throws
        IOException + IOException on error.
        - @@ -984,7 +991,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - OutputStream + OutputStream getOutputStream() @@ -1012,7 +1019,7 @@ onkeyup="return search_changed(event, false, '/')" />
        Throws
        IOException + IOException on error.
        - @@ -1033,7 +1040,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - ParcelFileDescriptor + ParcelFileDescriptor getParcelFileDescriptor() @@ -1057,7 +1064,7 @@ onkeyup="return search_changed(event, false, '/')" />
        Throws
        IOException + IOException on error.
        - diff --git a/docs/html/reference/com/google/android/gms/games/achievement/Achievement.html b/docs/html/reference/com/google/android/gms/games/achievement/Achievement.html index 805c1599dd180830945af292e994e595bcd0b39f..423da0550d07631c644990020b9f084e6f776916 100644 --- a/docs/html/reference/com/google/android/gms/games/achievement/Achievement.html +++ b/docs/html/reference/com/google/android/gms/games/achievement/Achievement.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +Achievement | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -952,7 +959,7 @@ From class - Iterator<T> + Iterator<T> @@ -1042,7 +1049,7 @@ From class final - Class<?> + Class<?> - + @@ -884,7 +891,7 @@ Summary: - String + String @@ -1107,7 +1114,7 @@ From class final - Class<?> + Class<?> - + @@ -952,7 +959,7 @@ From class - Iterator<T> + Iterator<T> @@ -1042,7 +1049,7 @@ From class final - Class<?> + Class<?> - + @@ -784,7 +791,7 @@ Summary: public final - String + String @@ -838,7 +845,7 @@ Summary: @@ -864,7 +871,7 @@ Summary: - String + String @@ -962,7 +969,7 @@ From class final - Class<?> + Class<?> - + @@ -815,7 +822,7 @@ Summary: @@ -859,7 +866,7 @@ Summary: - String + String @@ -1029,7 +1036,7 @@ From class final - Class<?> + Class<?> @@ -1129,7 +1136,7 @@ From interface - ArrayList<Participant> + ArrayList<Participant> - + @@ -884,7 +891,7 @@ Summary: - String + String @@ -1107,7 +1114,7 @@ From class final - Class<?> + Class<?> - + @@ -881,7 +885,7 @@ android.os.Parcelable public static final - InvitationEntityCreator + Creator<InvitationEntity> @@ -932,7 +936,7 @@ android.os.Parcelable boolean @@ -999,7 +1003,7 @@ android.os.Parcelable - String + String @@ -1135,63 +1139,6 @@ android.os.Parcelable - -
        IOException + IOException in case of error.
        getAchievementId() @@ -829,7 +836,7 @@ onkeyup="return search_changed(event, false, '/')" /> - String + String getDescription() @@ -850,7 +857,7 @@ onkeyup="return search_changed(event, false, '/')" /> void - getDescription(CharArrayBuffer dataOut) + getDescription(CharArrayBuffer dataOut)
        Loads the achievement description into the given CharArrayBuffer.
        @@ -868,7 +875,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - getFormattedCurrentSteps(CharArrayBuffer dataOut) + getFormattedCurrentSteps(CharArrayBuffer dataOut)
        Retrieves the number of steps this user has gone toward unlocking this achievement (formatted for the user's locale) into the given CharArrayBuffer.
        @@ -884,7 +891,7 @@ onkeyup="return search_changed(event, false, '/')" /> - String + String
        getFormattedCurrentSteps() @@ -907,7 +914,7 @@ onkeyup="return search_changed(event, false, '/')" /> void - getFormattedTotalSteps(CharArrayBuffer dataOut) + getFormattedTotalSteps(CharArrayBuffer dataOut)
        Loads the total number of steps necessary to unlock this achievement (formatted for the user's locale) into the given CharArrayBuffer; only applicable for @@ -924,7 +931,7 @@ onkeyup="return search_changed(event, false, '/')" /> - String + String
        getFormattedTotalSteps() @@ -965,7 +972,7 @@ onkeyup="return search_changed(event, false, '/')" /> void - getName(CharArrayBuffer dataOut) + getName(CharArrayBuffer dataOut)
        Loads the achievement name into the given CharArrayBuffer.
        @@ -980,7 +987,7 @@ onkeyup="return search_changed(event, false, '/')" /> - String + String
        getName() @@ -1016,7 +1023,7 @@ onkeyup="return search_changed(event, false, '/')" /> - Uri + Uri getRevealedImageUri() @@ -1091,7 +1098,7 @@ onkeyup="return search_changed(event, false, '/')" /> - Uri + Uri getUnlockedImageUri() @@ -1361,7 +1368,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getAchievementId () @@ -1430,7 +1437,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getDescription () @@ -1467,7 +1474,7 @@ onkeyup="return search_changed(event, false, '/')" /> void getDescription - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1506,7 +1513,7 @@ onkeyup="return search_changed(event, false, '/')" /> void getFormattedCurrentSteps - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1543,7 +1550,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getFormattedCurrentSteps () @@ -1582,7 +1589,7 @@ onkeyup="return search_changed(event, false, '/')" /> void getFormattedTotalSteps - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1620,7 +1627,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getFormattedTotalSteps () @@ -1693,7 +1700,7 @@ onkeyup="return search_changed(event, false, '/')" /> void getName - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1729,7 +1736,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getName () @@ -1800,7 +1807,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - Uri + Uri getRevealedImageUri () @@ -1943,7 +1950,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - Uri + Uri getUnlockedImageUri () diff --git a/docs/html/reference/com/google/android/gms/games/achievement/AchievementBuffer.html b/docs/html/reference/com/google/android/gms/games/achievement/AchievementBuffer.html index f5032744135a111129487b47da0c49740a0484ab..b50fe880049184ffc88c0c5a160cac3856ca4c7a 100644 --- a/docs/html/reference/com/google/android/gms/games/achievement/AchievementBuffer.html +++ b/docs/html/reference/com/google/android/gms/games/achievement/AchievementBuffer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + AchievementBuffer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        iterator() @@ -975,7 +982,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1013,7 +1020,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1106,7 +1113,7 @@ From class - String + String toString() @@ -1177,7 +1184,7 @@ From class class="jd-expando-trigger-img" /> From interface - java.lang.Iterable + java.lang.Iterable
        Iterator<T> + Iterator<T>
        iterator() diff --git a/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementUpdatedListener.html b/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementUpdatedListener.html index 1d5312f5378ea5635fd92c202cbd5d49ea00db0f..3eff16d86066cb184a3e84c9372a1afa8d9914b1 100644 --- a/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementUpdatedListener.html +++ b/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementUpdatedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + OnAchievementUpdatedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onAchievementUpdated(int statusCode, String achievementId) + onAchievementUpdated(int statusCode, String achievementId)
        Called when achievement data has been loaded.
        @@ -820,7 +827,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onAchievementUpdated - (int statusCode, String achievementId) + (int statusCode, String achievementId)
        diff --git a/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementsLoadedListener.html b/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementsLoadedListener.html index ff745b00fd305bf0f18efbab4cb59eb5f29181c9..7a66f0ea88699a5acd0eca6bff4e47cdbbea52c5 100644 --- a/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementsLoadedListener.html +++ b/docs/html/reference/com/google/android/gms/games/achievement/OnAchievementsLoadedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + OnAchievementsLoadedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getDisplayName() @@ -792,7 +799,7 @@ onkeyup="return search_changed(event, false, '/')" /> void - getDisplayName(CharArrayBuffer dataOut) + getDisplayName(CharArrayBuffer dataOut)
        Loads this leaderboard's display name into the given CharArrayBuffer.
        @@ -807,7 +814,7 @@ onkeyup="return search_changed(event, false, '/')" /> - Uri + Uri
        getIconImageUri() @@ -826,7 +833,7 @@ onkeyup="return search_changed(event, false, '/')" /> - String + String getLeaderboardId() @@ -862,7 +869,7 @@ onkeyup="return search_changed(event, false, '/')" /> - ArrayList<LeaderboardVariant> + ArrayList<LeaderboardVariant> getVariants() @@ -1015,7 +1022,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getDisplayName () @@ -1052,7 +1059,7 @@ onkeyup="return search_changed(event, false, '/')" /> void getDisplayName - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1088,7 +1095,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - Uri + Uri getIconImageUri () @@ -1126,7 +1133,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getLeaderboardId () @@ -1195,7 +1202,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - ArrayList<LeaderboardVariant> + ArrayList<LeaderboardVariant> getVariants () diff --git a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardBuffer.html b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardBuffer.html index add3ff47ef2962f0cb4f02edd81657ad9b1ba63a..7bd82fbea80b8a41c707cb5346ba091bb0fd80a2 100644 --- a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardBuffer.html +++ b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardBuffer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LeaderboardBuffer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        getPrimaryDataMarkerColumn() @@ -1017,7 +1024,7 @@ From class - Iterator<T> + Iterator<T> iterator() @@ -1040,7 +1047,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1078,7 +1085,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1171,7 +1178,7 @@ From class - String + String toString() @@ -1242,7 +1249,7 @@ From class class="jd-expando-trigger-img" /> From interface - java.lang.Iterable + java.lang.Iterable
        Iterator<T> + Iterator<T>
        iterator() @@ -1461,7 +1468,7 @@ From interface - String + String getPrimaryDataMarkerColumn () diff --git a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScore.html b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScore.html index 5a2898c3f3a3d0a1df3dca8041ac85d1862ad383..9ac6a192cb4c2e0fd443d1788600841b9b2f12f8 100644 --- a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScore.html +++ b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScore.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LeaderboardScore | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - getDisplayRank(CharArrayBuffer dataOut) + getDisplayRank(CharArrayBuffer dataOut)
        Load the formatted display rank into the given CharArrayBuffer.
        @@ -799,7 +806,7 @@ Summary: - String + String
        getDisplayRank() @@ -817,7 +824,7 @@ Summary: - String + String getDisplayScore() @@ -838,7 +845,7 @@ Summary: void - getDisplayScore(CharArrayBuffer dataOut) + getDisplayScore(CharArrayBuffer dataOut)
        Loads the formatted display score into the given CharArrayBuffer.
        @@ -907,7 +914,7 @@ Summary: - String + String
        getScoreHolderDisplayName() @@ -928,7 +935,7 @@ Summary: void - getScoreHolderDisplayName(CharArrayBuffer dataOut) + getScoreHolderDisplayName(CharArrayBuffer dataOut)
        Load the display name of the player who scored this score into the provided CharArrayBuffer.
        @@ -944,7 +951,7 @@ Summary: - Uri + Uri
        getScoreHolderHiResImageUri() @@ -962,7 +969,7 @@ Summary: - Uri + Uri getScoreHolderIconImageUri() @@ -1121,7 +1128,7 @@ From interface void getDisplayRank - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1157,7 +1164,7 @@ From interface abstract - String + String getDisplayRank () @@ -1192,7 +1199,7 @@ From interface abstract - String + String getDisplayScore () @@ -1230,7 +1237,7 @@ From interface void getDisplayScore - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1374,7 +1381,7 @@ From interface abstract - String + String getScoreHolderDisplayName () @@ -1412,7 +1419,7 @@ From interface void getScoreHolderDisplayName - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1449,7 +1456,7 @@ From interface abstract - Uri + Uri getScoreHolderHiResImageUri () @@ -1487,7 +1494,7 @@ From interface abstract - Uri + Uri getScoreHolderIconImageUri () diff --git a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScoreBuffer.html b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScoreBuffer.html index 29b23c2132f30aa36ddfc90f52b75f2dc6de3713..92d4402ac060a4416407190d85b1db75e3481e7d 100644 --- a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScoreBuffer.html +++ b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardScoreBuffer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LeaderboardScoreBuffer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        iterator() @@ -975,7 +982,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1013,7 +1020,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1106,7 +1113,7 @@ From class - String + String toString() @@ -1177,7 +1184,7 @@ From class class="jd-expando-trigger-img" /> From interface - java.lang.Iterable + java.lang.Iterable
        Iterator<T> + Iterator<T>
        iterator() diff --git a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardVariant.html b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardVariant.html index 5e213be2f69e2c0bd65aa77f57d82695ea10c071..f41bc766aced7d5164138c877c3bcf75b9478ed1 100644 --- a/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardVariant.html +++ b/docs/html/reference/com/google/android/gms/games/leaderboard/LeaderboardVariant.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LeaderboardVariant | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getDisplayPlayerRank() @@ -857,7 +864,7 @@ onkeyup="return search_changed(event, false, '/')" /> - String + String getDisplayPlayerScore() @@ -1410,7 +1417,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getDisplayPlayerRank () @@ -1446,7 +1453,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getDisplayPlayerScore () diff --git a/docs/html/reference/com/google/android/gms/games/leaderboard/OnLeaderboardMetadataLoadedListener.html b/docs/html/reference/com/google/android/gms/games/leaderboard/OnLeaderboardMetadataLoadedListener.html index f70264454b6a166facb09571acf4119f49a0e7dd..61c8647272f7693d8ed8b006801426b5bcc49714 100644 --- a/docs/html/reference/com/google/android/gms/games/leaderboard/OnLeaderboardMetadataLoadedListener.html +++ b/docs/html/reference/com/google/android/gms/games/leaderboard/OnLeaderboardMetadataLoadedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + OnLeaderboardMetadataLoadedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        formattedScore String containing the score data in a display-appropriate format.
        - SubmitScoreResult.Result(long rawScore, String formattedScore, boolean newBest) + SubmitScoreResult.Result(long rawScore, String formattedScore, boolean newBest)
        toString() @@ -895,7 +902,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -933,7 +940,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1026,7 +1033,7 @@ From class - String + String toString() @@ -1128,7 +1135,7 @@ From class public final - String + String formattedScore @@ -1230,7 +1237,7 @@ From class SubmitScoreResult.Result - (long rawScore, String formattedScore, boolean newBest) + (long rawScore, String formattedScore, boolean newBest)
        @@ -1271,7 +1278,7 @@ From class - String + String toString () diff --git a/docs/html/reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.html b/docs/html/reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.html index ca4cf75994b5653a80e41b11780c935d5dfb1d35..79566f916a9f6299a4cf9e56b0151d07be4d8362 100644 --- a/docs/html/reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.html +++ b/docs/html/reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + SubmitScoreResult | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - SubmitScoreResult(int statusCode, String leaderboardId, String playerId, HashMap<IntegerSubmitScoreResult.Result> results) + SubmitScoreResult(int statusCode, String leaderboardId, String playerId, HashMap<Integer, SubmitScoreResult.Result> results)
        Construct a new result describing a SubmitScore operation.
        @@ -833,7 +840,7 @@ Summary:
        - SubmitScoreResult(int statusCode, String leaderboardId, String playerId) + SubmitScoreResult(int statusCode, String leaderboardId, String playerId)
        getLeaderboardId() @@ -877,7 +884,7 @@ Summary: - String + String getPlayerId() @@ -931,7 +938,7 @@ Summary: - String + String toString() @@ -962,7 +969,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1000,7 +1007,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1093,7 +1100,7 @@ From class - String + String toString() @@ -1202,7 +1209,7 @@ From class SubmitScoreResult - (int statusCode, String leaderboardId, String playerId, HashMap<IntegerSubmitScoreResult.Result> results) + (int statusCode, String leaderboardId, String playerId, HashMap<Integer, SubmitScoreResult.Result> results)
        @@ -1253,7 +1260,7 @@ From class SubmitScoreResult - (int statusCode, String leaderboardId, String playerId) + (int statusCode, String leaderboardId, String playerId)
        @@ -1294,7 +1301,7 @@ From class - String + String getLeaderboardId () @@ -1328,7 +1335,7 @@ From class - String + String getPlayerId () @@ -1451,7 +1458,7 @@ From class - String + String toString () diff --git a/docs/html/reference/com/google/android/gms/games/leaderboard/package-summary.html b/docs/html/reference/com/google/android/gms/games/leaderboard/package-summary.html index 25ffcdfb39ec49ed42a0b9dee9ec354e39dfaac9..7f3d260fc3e1be46238aed67cfabb920707c3fe6 100644 --- a/docs/html/reference/com/google/android/gms/games/leaderboard/package-summary.html +++ b/docs/html/reference/com/google/android/gms/games/leaderboard/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + com.google.android.gms.games.leaderboard | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getInvitationId() @@ -990,7 +997,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
        getParticipants() @@ -1265,7 +1272,7 @@ From interface abstract - String + String getInvitationId () diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationBuffer.html b/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationBuffer.html index ae9ee177953fa1b9fcf0c6e723bd92f131a5bcb7..3d86e701318316e715699180b0667d3e5d0f72a4 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationBuffer.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationBuffer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + InvitationBuffer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        getPrimaryDataMarkerColumn() @@ -1017,7 +1024,7 @@ From class - Iterator<T> + Iterator<T> iterator() @@ -1040,7 +1047,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1078,7 +1085,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1171,7 +1178,7 @@ From class - String + String toString() @@ -1242,7 +1249,7 @@ From class class="jd-expando-trigger-img" /> From interface - java.lang.Iterable + java.lang.Iterable
        Iterator<T> + Iterator<T>
        iterator() @@ -1461,7 +1468,7 @@ From interface - String + String getPrimaryDataMarkerColumn () diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationEntity.html b/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationEntity.html index 65b1d8fdb2f2a2d90fb61f2421623a058fd9bcec..3a5bff51c50c8c42843f73ddb70a8c357bf57ba0 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationEntity.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/InvitationEntity.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + InvitationEntity | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        CREATOR
        - equals(Object obj) + equals(Object obj)
        getInvitationId() @@ -1035,7 +1039,7 @@ android.os.Parcelable - ArrayList<Participant> + ArrayList<Participant> getParticipants() @@ -1105,7 +1109,7 @@ android.os.Parcelable - String + String toString() @@ -1124,7 +1128,7 @@ android.os.Parcelable void - writeToParcel(Parcel dest, int flags) + writeToParcel(Parcel dest, int flags)
        - - - - - - - - - - - - - - - - - - - - - - -
        Protected Methods
        - - - - static - - ClassLoader - - getUnparcelClassLoader() - -
        - - - - static - - Integer - - getUnparcelClientVersion() - -
        - - - - - - boolean - - shouldDowngrade() - -
        - - @@ -1209,7 +1156,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object clone() @@ -1247,7 +1194,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0) @@ -1276,7 +1223,7 @@ From class final - Class<?> + Class<?> getClass() @@ -1340,7 +1287,7 @@ From class - String + String toString() @@ -1411,7 +1358,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1) @@ -1586,7 +1533,7 @@ From interface - String + String getInvitationId() @@ -1666,7 +1613,7 @@ From interface - ArrayList<Participant> + ArrayList<Participant> getParticipants() @@ -1722,7 +1669,7 @@ From interface public static final - InvitationEntityCreator + Creator<InvitationEntity> CREATOR @@ -1801,7 +1748,7 @@ From interface boolean equals - (Object obj) + (Object obj)
        @@ -1942,7 +1889,7 @@ From interface - String + String getInvitationId () @@ -2010,7 +1957,7 @@ From interface - ArrayList<Participant> + ArrayList<Participant> getParticipants () @@ -2147,7 +2094,7 @@ From interface - String + String toString () @@ -2179,7 +2126,7 @@ From interface void writeToParcel - (Parcel dest, int flags) + (Parcel dest, int flags)
        @@ -2200,113 +2147,6 @@ From interface -

        Protected Methods

        - - - - - -
        -

        - - protected - static - - - - ClassLoader - - getUnparcelClassLoader - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The ClassLoader to use for unparceling, if any. -
        -
        - -
        -
        - - - - -
        -

        - - protected - static - - - - Integer - - getUnparcelClientVersion - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The client version to use for unparceling, if known. -
        -
        - -
        -
        - - - - -
        -

        - - protected - - - - - boolean - - shouldDowngrade - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • Whether or not this object has been downgraded to hand back to a client. -
        -
        - -
        -
        - - - diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/OnInvitationReceivedListener.html b/docs/html/reference/com/google/android/gms/games/multiplayer/OnInvitationReceivedListener.html index 0f8e57ee1ada58b7a08d7c8545159a6ee2fffd3b..b43953e249e81426fd1be26c49cbaadfa5b0c4a7 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/OnInvitationReceivedListener.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/OnInvitationReceivedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + OnInvitationReceivedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -681,9 +688,6 @@ Summary: - | Protected Methods - - | Inherited Methods @@ -712,7 +716,7 @@ Summary: - extends Object
        + extends Object
        @@ -721,7 +725,7 @@ Summary: implements - Parcelable + Parcelable Participant @@ -739,7 +743,7 @@ Summary: - java.lang.Object + java.lang.Object @@ -924,7 +928,7 @@ android.os.Parcelable public static final - ParticipantEntityCreator + Creator<ParticipantEntity> CREATOR @@ -975,7 +979,7 @@ android.os.Parcelable boolean - equals(Object obj) + equals(Object obj) @@ -1006,7 +1010,7 @@ android.os.Parcelable - String + String getDisplayName() @@ -1027,7 +1031,7 @@ android.os.Parcelable void - getDisplayName(CharArrayBuffer dataOut) + getDisplayName(CharArrayBuffer dataOut)
        Loads the display name for this participant into the provided CharArrayBuffer.
        @@ -1042,7 +1046,7 @@ android.os.Parcelable - Uri + Uri getHiResImageUri() @@ -1060,7 +1064,7 @@ android.os.Parcelable - Uri + Uri getIconImageUri() @@ -1078,7 +1082,7 @@ android.os.Parcelable - String + String getParticipantId() @@ -1184,7 +1188,7 @@ android.os.Parcelable - String + String toString() @@ -1203,7 +1207,7 @@ android.os.Parcelable void - writeToParcel(Parcel dest, int flags) + writeToParcel(Parcel dest, int flags) @@ -1214,63 +1218,6 @@ android.os.Parcelable - - - - - - - - - - - - - - - - - - - - - - - - -
        Protected Methods
        - - - - static - - ClassLoader - - getUnparcelClassLoader() - -
        - - - - static - - Integer - - getUnparcelClientVersion() - -
        - - - - - - boolean - - shouldDowngrade() - -
        - - @@ -1288,7 +1235,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object clone() @@ -1326,7 +1273,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0) @@ -1355,7 +1302,7 @@ From class final - Class<?> + Class<?> getClass() @@ -1419,7 +1366,7 @@ From class - String + String toString() @@ -1490,7 +1437,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1) @@ -1629,7 +1576,7 @@ From interface - String + String getDisplayName() @@ -1650,7 +1597,7 @@ From interface void - getDisplayName(CharArrayBuffer dataOut) + getDisplayName(CharArrayBuffer dataOut)
        Loads the display name for this participant into the provided CharArrayBuffer.
        @@ -1665,7 +1612,7 @@ From interface - Uri + Uri getHiResImageUri() @@ -1683,7 +1630,7 @@ From interface - Uri + Uri getIconImageUri() @@ -1701,7 +1648,7 @@ From interface - String + String getParticipantId() @@ -1811,7 +1758,7 @@ From interface public static final - ParticipantEntityCreator + Creator<ParticipantEntity> CREATOR @@ -1890,7 +1837,7 @@ From interface boolean equals - (Object obj) + (Object obj)
        @@ -1963,7 +1910,7 @@ From interface - String + String getDisplayName () @@ -2001,7 +1948,7 @@ From interface void getDisplayName - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -2037,7 +1984,7 @@ From interface - Uri + Uri getHiResImageUri () @@ -2074,7 +2021,7 @@ From interface - Uri + Uri getIconImageUri () @@ -2112,7 +2059,7 @@ From interface - String + String getParticipantId () @@ -2318,7 +2265,7 @@ From interface - String + String toString () @@ -2350,7 +2297,7 @@ From interface void writeToParcel - (Parcel dest, int flags) + (Parcel dest, int flags)
        @@ -2371,113 +2318,6 @@ From interface -

        Protected Methods

        - - - - - -
        -

        - - protected - static - - - - ClassLoader - - getUnparcelClassLoader - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The ClassLoader to use for unparceling, if any. -
        -
        - -
        -
        - - - - -
        -

        - - protected - static - - - - Integer - - getUnparcelClientVersion - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The client version to use for unparceling, if known. -
        -
        - -
        -
        - - - - -
        -

        - - protected - - - - - boolean - - shouldDowngrade - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • Whether or not this object has been downgraded to hand back to a client. -
        -
        - -
        -
        - - - diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/ParticipantUtils.html b/docs/html/reference/com/google/android/gms/games/multiplayer/ParticipantUtils.html index f8720d08983aa5ef4bf2c01395a50b32ffc66a0f..c54331507e1964a81527dee439d7d39b27cb8902 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/ParticipantUtils.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/ParticipantUtils.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + ParticipantUtils | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -696,7 +703,7 @@ Summary: - extends Object
        + extends Object
        @@ -705,7 +712,7 @@ Summary: implements - Parcelable + Parcelable @@ -721,7 +728,7 @@ Summary: - java.lang.Object + java.lang.Object @@ -872,7 +879,7 @@ android.os.Parcelable public static final - Creator<RealTimeMessage> + Creator<RealTimeMessage> CREATOR @@ -936,7 +943,7 @@ android.os.Parcelable - String + String getSenderParticipantId() @@ -971,7 +978,7 @@ android.os.Parcelable void - writeToParcel(Parcel parcel, int flag) + writeToParcel(Parcel parcel, int flag) @@ -999,7 +1006,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object clone() @@ -1037,7 +1044,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0) @@ -1066,7 +1073,7 @@ From class final - Class<?> + Class<?> getClass() @@ -1130,7 +1137,7 @@ From class - String + String toString() @@ -1201,7 +1208,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1) @@ -1373,7 +1380,7 @@ From interface public static final - Creator<RealTimeMessage> + Creator<RealTimeMessage> CREATOR @@ -1483,7 +1490,7 @@ From interface - String + String getSenderParticipantId () @@ -1554,7 +1561,7 @@ From interface void writeToParcel - (Parcel parcel, int flag) + (Parcel parcel, int flag)
        diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessageReceivedListener.html b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessageReceivedListener.html index a5edcac6719be81565e9fccb0fa9e23604e3ef74..2099b6a5ac33a8f3522f855589616aee7516eb28 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessageReceivedListener.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessageReceivedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + RealTimeMessageReceivedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -703,7 +710,7 @@ Summary: implements - Parcelable + Parcelable Freezable<T> @@ -936,7 +943,7 @@ android.os.Parcelable - Bundle + Bundle getAutoMatchCriteria() @@ -954,6 +961,26 @@ android.os.Parcelable + int + + + getAutoMatchWaitEstimateSeconds() + +
        Retrieves the estimated wait time for automatching to finish for players who are not + automatched immediately, as measured from the time that the room entered the + automatching pool.
        + + + + + + + + abstract + + + + long @@ -963,14 +990,14 @@ android.os.Parcelable - + abstract - String + String getCreatorId() @@ -979,14 +1006,14 @@ android.os.Parcelable - + abstract - String + String getDescription() @@ -995,7 +1022,7 @@ android.os.Parcelable - + abstract @@ -1005,7 +1032,7 @@ android.os.Parcelable void - getDescription(CharArrayBuffer dataOut) + getDescription(CharArrayBuffer dataOut)
        Loads the room description into the given CharArrayBuffer.
        @@ -1013,17 +1040,17 @@ android.os.Parcelable - + abstract - String + String - getParticipantId(String playerId) + getParticipantId(String playerId)
        Get the participant ID for a given player.
        @@ -1031,14 +1058,14 @@ android.os.Parcelable - + abstract - ArrayList<String> + ArrayList<String> getParticipantIds() @@ -1047,7 +1074,7 @@ android.os.Parcelable - + abstract @@ -1057,7 +1084,7 @@ android.os.Parcelable int - getParticipantStatus(String participantId) + getParticipantStatus(String participantId)
        Get the status of a participant in a room.
        @@ -1065,14 +1092,14 @@ android.os.Parcelable - + abstract - String + String getRoomId() @@ -1081,7 +1108,7 @@ android.os.Parcelable - + abstract @@ -1097,7 +1124,7 @@ android.os.Parcelable - + abstract @@ -1135,7 +1162,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1) @@ -1274,7 +1301,7 @@ From interface - ArrayList<Participant> + ArrayList<Participant> getParticipants() @@ -1548,7 +1575,7 @@ From interface abstract - Bundle + Bundle getAutoMatchCriteria () @@ -1573,6 +1600,43 @@ From interface
        + + +
        +

        + + public + + + abstract + + int + + getAutoMatchWaitEstimateSeconds + () +

        +
        +
        + + + +
        +
        + +

        Retrieves the estimated wait time for automatching to finish for players who are not + automatched immediately, as measured from the time that the room entered the + automatching pool.

        +
        +
        Returns
        +
        • The estimated wait time in seconds, or -1 if the room is not + automatching or no estimate could be provided. +
        +
        + +
        +
        + +
        @@ -1617,7 +1681,7 @@ From interface abstract - String + String getCreatorId () @@ -1651,7 +1715,7 @@ From interface abstract - String + String getDescription () @@ -1688,7 +1752,7 @@ From interface void getDescription - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -1724,10 +1788,10 @@ From interface abstract - String + String getParticipantId - (String playerId) + (String playerId)
        @@ -1769,7 +1833,7 @@ From interface abstract - ArrayList<String> + ArrayList<String> getParticipantIds () @@ -1807,7 +1871,7 @@ From interface int getParticipantStatus - (String participantId) + (String participantId)
        @@ -1859,7 +1923,7 @@ From interface abstract - String + String getRoomId () diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.Builder.html b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.Builder.html index 17c3c86e277a1dc257d1550dc7fb0a2ddc9f7e9f..d97beb9434b9de4f1321ba25fce87643c0293a11 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.Builder.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + RoomConfig.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -681,7 +688,7 @@ Summary: - extends Object
        + extends Object
        @@ -701,7 +708,7 @@ Summary: - java.lang.Object + java.lang.Object @@ -831,7 +838,7 @@ Summary: static - Bundle + Bundle createAutoMatchCriteria(int minAutoMatchPlayers, int maxAutoMatchPlayers, long exclusiveBitMask) @@ -849,7 +856,7 @@ Summary: - Bundle + Bundle getAutoMatchCriteria() @@ -867,7 +874,7 @@ Summary: - String + String getInvitationId() @@ -885,7 +892,7 @@ Summary: - String[] + String[] getInvitedPlayerIds() @@ -1008,7 +1015,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object clone() @@ -1046,7 +1053,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0) @@ -1075,7 +1082,7 @@ From class final - Class<?> + Class<?> getClass() @@ -1139,7 +1146,7 @@ From class - String + String toString() @@ -1297,7 +1304,7 @@ From class - Bundle + Bundle createAutoMatchCriteria (int minAutoMatchPlayers, int maxAutoMatchPlayers, long exclusiveBitMask) @@ -1351,7 +1358,7 @@ From class - Bundle + Bundle getAutoMatchCriteria () @@ -1385,7 +1392,7 @@ From class - String + String getInvitationId () @@ -1420,7 +1427,7 @@ From class - String[] + String[] getInvitedPlayerIds () diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomEntity.html b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomEntity.html index b2e7fb1086557aa858b3f0da32d49bf6ac547bf0..d9c66e3a938ad616164eff874898250ddf8dabc2 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomEntity.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomEntity.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + RoomEntity | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
    • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
    • Google Services
    • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        +
      • + Overview +
      • Getting Started
      • -
      • - Architectural Overview +
      • + Implementing GCM Client
      • -
      • - Cloud Connection Server +
      • User Notifications
      • -
      • - GCM Client -
      • -
      • - GCM Server -
      • Advanced Topics
      • @@ -688,9 +695,6 @@ Summary: - | Protected Methods - - | Inherited Methods @@ -719,7 +723,7 @@ Summary: - extends Object
        + extends Object
        @@ -728,7 +732,7 @@ Summary: implements - Parcelable + Parcelable Room @@ -746,7 +750,7 @@ Summary: - java.lang.Object + java.lang.Object @@ -944,7 +948,7 @@ android.os.Parcelable public static final - RoomEntityCreator + Creator<RoomEntity> CREATOR @@ -995,7 +999,7 @@ android.os.Parcelable boolean - equals(Object obj) + equals(Object obj) @@ -1026,7 +1030,7 @@ android.os.Parcelable - Bundle + Bundle getAutoMatchCriteria() @@ -1044,11 +1048,15 @@ android.os.Parcelable - long + int - getCreationTimestamp() + getAutoMatchWaitEstimateSeconds() +
        Retrieves the estimated wait time for automatching to finish for players who are not + automatched immediately, as measured from the time that the room entered the + automatching pool.
        + @@ -1060,10 +1068,10 @@ android.os.Parcelable - String + long - getCreatorId() + getCreationTimestamp() @@ -1076,10 +1084,10 @@ android.os.Parcelable - String + String - getDescription() + getCreatorId() @@ -1092,13 +1100,11 @@ android.os.Parcelable - void + String - getDescription(CharArrayBuffer dataOut) + getDescription() -
        Loads the room description into the given CharArrayBuffer.
        - @@ -1110,12 +1116,12 @@ android.os.Parcelable - String + void - getParticipantId(String playerId) + getDescription(CharArrayBuffer dataOut) -
        Get the participant ID for a given player.
        +
        Loads the room description into the given CharArrayBuffer.
        @@ -1128,11 +1134,13 @@ android.os.Parcelable - ArrayList<String> + String - getParticipantIds() + getParticipantId(String playerId) +
        Get the participant ID for a given player.
        + @@ -1144,13 +1152,11 @@ android.os.Parcelable - int + ArrayList<String> - getParticipantStatus(String participantId) + getParticipantIds() -
        Get the status of a participant in a room.
        - @@ -1162,12 +1168,12 @@ android.os.Parcelable - ArrayList<Participant> + int - getParticipants() + getParticipantStatus(String participantId) -
        Retrieve the Participants for this object.
        +
        Get the status of a participant in a room.
        @@ -1180,11 +1186,13 @@ android.os.Parcelable - String + ArrayList<Participant> - getRoomId() + getParticipants() +
        Retrieve the Participants for this object.
        + @@ -1196,10 +1204,10 @@ android.os.Parcelable - int + String - getStatus() + getRoomId() @@ -1215,7 +1223,7 @@ android.os.Parcelable int - getVariant() + getStatus() @@ -1231,7 +1239,7 @@ android.os.Parcelable int - hashCode() + getVariant() @@ -1244,13 +1252,11 @@ android.os.Parcelable - boolean + int - isDataValid() + hashCode() -
        Check to see if this object is valid for use.
        - @@ -1262,11 +1268,13 @@ android.os.Parcelable - String + boolean - toString() + isDataValid() +
        Check to see if this object is valid for use.
        + @@ -1278,37 +1286,10 @@ android.os.Parcelable - void + String - writeToParcel(Parcel dest, int flags) - - - - - - - - - - - - - - - - - - - @@ -1319,28 +1300,12 @@ android.os.Parcelable - static - - Integer - - - - - - - @@ -1352,6 +1317,8 @@ android.os.Parcelable + +
        Protected Methods
        - - - - static - - ClassLoader - - getUnparcelClassLoader() + toString()
        - getUnparcelClientVersion() - -
        - - - - boolean + void - shouldDowngrade() + writeToParcel(Parcel dest, int flags)
        @@ -1433,7 +1400,7 @@ From class final - Class<?> + Class<?> @@ -1707,7 +1674,7 @@ From interface - ArrayList<Participant> + ArrayList<Participant> + + + + + + + + + + + + + + + - + @@ -728,25 +735,49 @@ Summary:

        Class Overview

        -

        Google Cloud Messaging for Android. +

        The class you use to write a GCM-enabled client application that runs on an Android device. + Client applications can receive GCM messages and optionally send messages of their own back to + the server. + +

        This class requires Google Play services version 3.1 or higher. For a + detailed discussion of how to write a GCM client app, see + + Implementing GCM Client. + +

        To send or receive messages, your application first needs to get a registration ID. The + registration ID identifies the device and application, and also determines which 3rd-party + application servers are allowed to send messages to this application instance. + +

        To get a registration ID, you must supply one or more sender IDs. A sender ID is a project + number you acquire from the API console, as described in + Getting Started. The sender ID is + used in the registration process to identify a 3rd-party application server that is permitted to + send messages to the device. The following snippet shows you how to call the + register() method. For a more comprehensive example, see + Implementing GCM Client. -

        This class requires Google Play services version 3.1 or higher. +

        + String SENDER_ID = "My-Sender-ID";
        + GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
        + String registrationId = gcm.register(SENDER_ID);
        + // Upload the registration ID to your own server
        + // The request to your server should be authenticated if your app is using accounts.
        + 
        -

        In order to receive GCM messages you need to declare a permission and a BroadcastReceiver - in your manifest. This is a backward-compatible subset of what was required in previous - versions. +

        In order to receive GCM messages, you need to declare a permission and a + BroadcastReceiver in your manifest. This is a backward-compatible subset of what was + required in previous versions of GCM.

        To allow the application to use GCM, add this permission to the manifest: -

        - <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
        + +
        <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

        GCM delivers messages as a broadcast. The receivers must be registered in the manifest in order to wake up the application. -

        The com.google.android.c2dm.permission.SEND permission is held by - Google Play services. - This prevents other code from invoking the broadcast receiver. - Here is an excerpt from the manifest: +

        The com.google.android.c2dm.permission.SEND permission is held by Google Play + services. This prevents other code from invoking the broadcast receiver. Here is an excerpt + from a sample manifest:

          <receiver android:name=".MyReceiver" android:exported="true"
        @@ -757,25 +788,42 @@ Summary:
              </intent-filter>
          </receiver>
        -

        To send or receive messages, you first need to get a registration ID. The registration ID - identifies the device and application, as well as which servers are allowed to send messages. +

        When a GCM connection server delivers the message to your client app, the + BroadcastReceiver receives the message as an intent. You can either process the + intent in the BroadcastReceiver, or you can pass off the work of processing the + intent to a service (typically, an IntentService). If you use a service, your + broadcast receiver should be an instance of WakefulBroadcastReceiver, to hold a + wake lock while the service is doing its work. -

        - GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
        + 

        When processing the intent GCM passes into your app's broadcase receiver, you can determine + the message type by calling getMessageType(intent). For example: - String registrationId = gcm.register(sender1, sender2); - // Upload the registrationId to your own server - // The request to your server should be authenticated if your app is using accounts. +

        + GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        + String messageType = gcm.getMessageType(intent);
        + ...
        + // Filter messages based on message type. It is likely that GCM will be extended in the future
        + // with new message types, so just ignore message types you're not interested in, or that you
        + // don't recognize.
        + if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
        +    // It's an error.
        + } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
        +    // Deleted messages on the server.
        + } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
        +    // It's a regular GCM message, do some work.
        + }
          
        -

        The BroadcastReceiver will be invoked whenever a message is received, as well as for special - messages generated by GCM. Within the BroadcastReceiver you can call - getMessageType(Intent). +

        If you are using the XMPP-based + Cloud Connection Server, your + client app can send upstream messages back to the server. For example: -

        To send messages, call send():

        - gcm.send(to, msgId, data);
        + gcm.send(SENDER_ID + "@gcm.googleapis.com", id, data);
          
        + + For a more details, see + Implementing GCM Client.

        @@ -822,14 +870,15 @@ Summary:
        - + - + - + @@ -837,7 +886,7 @@ Summary: - + @@ -845,14 +894,14 @@ Summary: - + - + @@ -936,7 +985,7 @@ Summary: GoogleCloudMessaging @@ -969,10 +1018,10 @@ Summary: - String + String @@ -1008,9 +1057,9 @@ Summary: void @@ -1056,7 +1105,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -1123,7 +1172,7 @@ From class final - Class<?> + Class<?> - + @@ -842,7 +849,7 @@ Summary: @@ -1172,7 +1179,7 @@ From class final - Class<?> + Class<?>"); - html.append(" "); - html.append(" "); - html.append(""); - } - - private void createResultsList( - StringBuilder html, String title, Cursor cursor) { - String relativePath; - String id = ""; - AbstractResult.ResultCode resultCode; - - html.append("

        " + title + " [" + cursor.getCount() + "]

        "); - - if (!cursor.moveToFirst()) { - return; - } - - AbstractResult result; - do { - result = SummarizerDBHelper.getAbstractResult(cursor); - - relativePath = result.getRelativePath(); - resultCode = result.getResultCode(); - - html.append("

        "); - - /** - * Technically, two different paths could end up being the same, because - * ':' is a valid character in a path. However, it is probably not going - * to cause any problems in this case - */ - id = relativePath.replace(File.separator, ":"); - - /** Write the test name */ - if (resultCode == AbstractResult.ResultCode.RESULTS_DIFFER) { - html.append(""); - html.append(""); - html.append("" + relativePath + ""); - html.append(""); - } else { - html.append(""); - html.append(""); - html.append("" + result.getRelativePath() + ""); - html.append(""); - } - - if (!result.didPass()) { - appendTags(html, result); - } - - html.append("

        "); - appendExpectedResultsSources(result, html); - - if (resultCode == AbstractResult.ResultCode.RESULTS_DIFFER) { - html.append("
        "); - html.append(result.getDiffAsHtml()); - html.append("Hide"); - html.append(" | "); - html.append("Show source"); - html.append("
        "); - } - - html.append("
        "); - - if (++mResultsSinceLastHtmlDump == RESULTS_PER_DUMP) { - dumpHtmlToFile(html, true); - } - - cursor.moveToNext(); - } while (!cursor.isAfterLast()); - } - - private void appendTags(StringBuilder html, AbstractResult result) { - /** Tag tests which crash, time out or where results don't match */ - if (result.didCrash()) { - html.append(" Crashed"); - } else { - if (result.didTimeOut()) { - html.append(" Timed out"); - } - AbstractResult.ResultCode resultCode = result.getResultCode(); - if (resultCode != AbstractResult.ResultCode.RESULTS_MATCH) { - html.append(" "); - html.append(resultCode.toString()); - html.append(""); - } - } - - /** Detect missing LTC function */ - String additionalTextOutputString = result.getAdditionalTextOutputString(); - if (additionalTextOutputString != null && - additionalTextOutputString.contains("com.android.dumprendertree") && - additionalTextOutputString.contains("has no method")) { - if (additionalTextOutputString.contains("LayoutTestController")) { - html.append(" LTC function missing"); - } - if (additionalTextOutputString.contains("EventSender")) { - html.append(" "); - html.append("ES function missing"); - } - } - } - - private static final void appendExpectedResultsSources(AbstractResult result, - StringBuilder html) { - String textSource = result.getExpectedTextResultPath(); - String imageSource = result.getExpectedImageResultPath(); - - if (result.didCrash()) { - html.append("Did not look for expected results"); - return; - } - - if (textSource == null) { - // Show if a text result is missing. We may want to revisit this decision when we add - // support for image results. - html.append("Expected textual result missing"); - } else { - html.append("Expected textual result from: "); - html.append(""); - html.append(textSource + ""); - } - if (imageSource != null) { - html.append("Expected image result from: "); - html.append(""); - html.append(imageSource + ""); - } - } - - private static final URL getViewSourceUrl(String relativePath) { - URL url = null; - try { - url = new URL("http", "localhost", ForwarderManager.HTTP_PORT, - "/Tools/DumpRenderTree/android/view_source.php?src=" + - relativePath); - } catch (MalformedURLException e) { - assert false : "relativePath=" + relativePath; - } - return url; - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/SummarizerDBHelper.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/SummarizerDBHelper.java deleted file mode 100644 index 23e13ec09e9fc358eb16d1467e7fc7552ee565f4..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/SummarizerDBHelper.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -import java.util.HashSet; -import java.util.Set; - -/** - * A basic class that wraps database accesses inside itself and provides functionality to - * store and retrieve AbstractResults. - */ -public class SummarizerDBHelper { - private static final String KEY_ID = "id"; - private static final String KEY_PATH = "path"; - private static final String KEY_BYTES = "bytes"; - - private static final String DATABASE_NAME = "SummarizerDB"; - private static final int DATABASE_VERSION = 1; - - static final String EXPECTED_FAILURES_TABLE = "expectedFailures"; - static final String UNEXPECTED_FAILURES_TABLE = "unexpectedFailures"; - static final String EXPECTED_PASSES_TABLE = "expextedPasses"; - static final String UNEXPECTED_PASSES_TABLE = "unexpextedPasses"; - private static final Set TABLES_NAMES = new HashSet(); - { - TABLES_NAMES.add(EXPECTED_FAILURES_TABLE); - TABLES_NAMES.add(EXPECTED_PASSES_TABLE); - TABLES_NAMES.add(UNEXPECTED_FAILURES_TABLE); - TABLES_NAMES.add(UNEXPECTED_PASSES_TABLE); - } - - private static final void createTables(SQLiteDatabase db) { - String cmd; - for (String tableName : TABLES_NAMES) { - cmd = "create table " + tableName + " (" - + KEY_ID + " integer primary key autoincrement, " - + KEY_PATH + " text not null, " - + KEY_BYTES + " blob not null);"; - db.execSQL(cmd); - } - } - - private static final void dropTables(SQLiteDatabase db) { - for (String tableName : TABLES_NAMES) { - db.execSQL("DROP TABLE IF EXISTS " + tableName); - } - } - - private static class DatabaseHelper extends SQLiteOpenHelper { - DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - dropTables(db); - createTables(db); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - /** NOOP for now, because we will never upgrade the db */ - } - - public void reset(SQLiteDatabase db) { - dropTables(db); - createTables(db); - } - } - - private DatabaseHelper mDbHelper; - private SQLiteDatabase mDb; - - private final Context mContext; - - public SummarizerDBHelper(Context ctx) { - mContext = ctx; - mDbHelper = new DatabaseHelper(mContext); - } - - public void reset() { - mDbHelper.reset(this.mDb); - } - - public void open() throws SQLException { - mDb = mDbHelper.getWritableDatabase(); - } - - public void close() { - mDbHelper.close(); - } - - public void insertAbstractResult(AbstractResult result, String table) { - ContentValues cv = new ContentValues(); - cv.put(KEY_PATH, result.getRelativePath()); - cv.put(KEY_BYTES, result.getBytes()); - mDb.insert(table, null, cv); - } - - public Cursor getAbstractResults(String table) throws SQLException { - return mDb.query(false, table, new String[] {KEY_BYTES}, null, null, null, null, - KEY_PATH + " ASC", null); - } - - public static AbstractResult getAbstractResult(Cursor cursor) { - return AbstractResult.create(cursor.getBlob(cursor.getColumnIndex(KEY_BYTES))); - } -} \ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java deleted file mode 100644 index e374c1bbefc735ff10acf93d0856b6045ec9261c..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2; - -import com.android.dumprendertree2.scriptsupport.OnEverythingFinishedCallback; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.view.Gravity; -import android.view.Window; -import android.webkit.WebView; -import android.widget.Toast; - -import java.io.File; -import java.util.ArrayList; - -/** - * An Activity that generates a list of tests and sends the intent to - * LayoutTestsExecuter to run them. It also restarts the LayoutTestsExecuter - * after it crashes. - */ -public class TestsListActivity extends Activity { - - private static final int MSG_TEST_LIST_PRELOADER_DONE = 0; - - /** Constants for adding extras to an intent */ - public static final String EXTRA_TEST_PATH = "TestPath"; - - private static ProgressDialog sProgressDialog; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_TEST_LIST_PRELOADER_DONE: - sProgressDialog.dismiss(); - mTestsList = (ArrayList)msg.obj; - mTotalTestCount = mTestsList.size(); - restartExecutor(0); - break; - } - } - }; - - private ArrayList mTestsList; - private int mTotalTestCount; - - private OnEverythingFinishedCallback mOnEverythingFinishedCallback; - private boolean mEverythingFinished; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - /** Prepare the progress dialog */ - sProgressDialog = new ProgressDialog(TestsListActivity.this); - sProgressDialog.setCancelable(false); - sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - sProgressDialog.setTitle(R.string.dialog_progress_title); - sProgressDialog.setMessage(getText(R.string.dialog_progress_msg)); - - requestWindowFeature(Window.FEATURE_PROGRESS); - - Intent intent = getIntent(); - if (!intent.getAction().equals(Intent.ACTION_RUN)) { - return; - } - String path = intent.getStringExtra(EXTRA_TEST_PATH); - - sProgressDialog.show(); - Message doneMsg = Message.obtain(mHandler, MSG_TEST_LIST_PRELOADER_DONE); - - Intent serviceIntent = new Intent(this, ManagerService.class); - serviceIntent.putExtra("path", path); - startService(serviceIntent); - - new TestsListPreloaderThread(path, doneMsg).start(); - } - - @Override - protected void onNewIntent(Intent intent) { - if (intent.getAction().equals(Intent.ACTION_REBOOT)) { - onCrashIntent(intent); - } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { - onEverythingFinishedIntent(intent); - } - } - - /** - * This method handles an intent that comes from ManageService when crash is detected. - * The intent contains an index in mTestsList of the test that crashed. TestsListActivity - * restarts the LayoutTestsExecutor from the following test in mTestsList, by sending - * an intent to it. This new intent contains a list of remaining tests to run, - * total count of all tests, and the index of the first test to run after restarting. - * LayoutTestExecutor runs then as usual, sending reports to ManagerService. If it - * detects the crash it sends a new intent and the flow repeats. - */ - private void onCrashIntent(Intent intent) { - int nextTestToRun = intent.getIntExtra("crashedTestIndex", -1) + 1; - if (nextTestToRun > 0 && nextTestToRun <= mTotalTestCount) { - restartExecutor(nextTestToRun); - } - } - - public void registerOnEverythingFinishedCallback(OnEverythingFinishedCallback callback) { - mOnEverythingFinishedCallback = callback; - if (mEverythingFinished) { - mOnEverythingFinishedCallback.onFinished(); - } - } - - private void onEverythingFinishedIntent(Intent intent) { - Toast toast = Toast.makeText(this, - "All tests finished.\nPress back key to return to the tests' list.", - Toast.LENGTH_LONG); - toast.setGravity(Gravity.CENTER, -40, 0); - toast.show(); - - /** Show the details to the user */ - WebView webView = new WebView(this); - webView.getSettings().setJavaScriptEnabled(true); - webView.getSettings().setBuiltInZoomControls(true); - webView.getSettings().setEnableSmoothTransition(true); - /** This enables double-tap to zoom */ - webView.getSettings().setUseWideViewPort(true); - - setContentView(webView); - webView.loadUrl(Summarizer.getDetailsUri().toString()); - - mEverythingFinished = true; - if (mOnEverythingFinishedCallback != null) { - mOnEverythingFinishedCallback.onFinished(); - } - } - - /** - * This, together with android:configChanges="orientation" in manifest file, prevents - * the activity from restarting on orientation change. - */ - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - outState.putStringArrayList("testsList", mTestsList); - outState.putInt("totalCount", mTotalTestCount); - - super.onSaveInstanceState(outState); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - - mTestsList = savedInstanceState.getStringArrayList("testsList"); - mTotalTestCount = savedInstanceState.getInt("totalCount"); - } - - /** - * (Re)starts the executer activity from the given test number (inclusive, 0-based). - * This number is an index in mTestsList, not the sublist passed in the intent. - * - * @param startFrom - * test index in mTestsList to start the tests from (inclusive, 0-based) - */ - private void restartExecutor(int startFrom) { - Intent intent = new Intent(); - intent.setClass(this, LayoutTestsExecutor.class); - intent.setAction(Intent.ACTION_RUN); - - if (startFrom < mTotalTestCount) { - File testListFile = new File(getExternalFilesDir(null), "test_list.txt"); - FsUtils.saveTestListToStorage(testListFile, startFrom, mTestsList); - intent.putExtra(LayoutTestsExecutor.EXTRA_TESTS_FILE, testListFile.getAbsolutePath()); - intent.putExtra(LayoutTestsExecutor.EXTRA_TEST_INDEX, startFrom); - } else { - intent.putExtra(LayoutTestsExecutor.EXTRA_TESTS_FILE, ""); - } - - startActivity(intent); - } -} \ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java deleted file mode 100644 index ab9883070a767c658ccb5ea075858e17e97cc738..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2; - -import android.os.Environment; -import android.os.Message; -import java.io.File; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * A Thread that is responsible for generating a lists of tests to run. - */ -public class TestsListPreloaderThread extends Thread { - - private static final String LOG_TAG = "TestsListPreloaderThread"; - - /** A list containing relative paths of tests to run */ - private ArrayList mTestsList = new ArrayList(); - - private FileFilter mFileFilter; - - /** - * A relative path to the directory with the tests we want to run or particular test. - * Used up to and including preloadTests(). - */ - private String mRelativePath; - - private Message mDoneMsg; - - /** - * The given path must be relative to the root dir. - * - * @param path - * @param doneMsg - */ - public TestsListPreloaderThread(String path, Message doneMsg) { - mRelativePath = path; - mDoneMsg = doneMsg; - } - - @Override - public void run() { - mFileFilter = new FileFilter(); - if (FileFilter.isTestFile(mRelativePath)) { - mTestsList.add(mRelativePath); - } else { - loadTestsFromUrl(mRelativePath); - } - - mDoneMsg.obj = mTestsList; - mDoneMsg.sendToTarget(); - } - - /** - * Loads all the tests from the given directories and all the subdirectories - * into mTestsList. - * - * @param dirRelativePath - */ - private void loadTestsFromUrl(String rootRelativePath) { - LinkedList directoriesList = new LinkedList(); - directoriesList.add(rootRelativePath); - - String relativePath; - String itemName; - while (!directoriesList.isEmpty()) { - relativePath = directoriesList.removeFirst(); - - List dirRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, true); - if (dirRelativePaths != null) { - for (String dirRelativePath : dirRelativePaths) { - itemName = new File(dirRelativePath).getName(); - if (FileFilter.isTestDir(itemName)) { - directoriesList.add(dirRelativePath); - } - } - } - - List testRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, false); - if (testRelativePaths != null) { - for (String testRelativePath : testRelativePaths) { - itemName = new File(testRelativePath).getName(); - if (FileFilter.isTestFile(itemName)) { - /** We choose to skip all the tests that are expected to crash. */ - if (!mFileFilter.isCrash(testRelativePath)) { - mTestsList.add(testRelativePath); - } else { - /** - * TODO: Summarizer is now in service - figure out how to send the info. - * Previously: mSummarizer.addSkippedTest(relativePath); - */ - } - } - } - } - } - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java deleted file mode 100644 index fd1c0ad4499478ef0d72e533ae3a7c2a8f0599a5..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.webkit.WebView; -import android.webkit.WebViewClassic; - -import name.fraser.neil.plaintext.diff_match_patch; - -import java.util.LinkedList; - -/** - * A result object for which the expected output is text. It does not have an image - * expected result. - * - *

        Created if layoutTestController.dumpAsText() was called. - */ -public class TextResult extends AbstractResult { - - private static final int MSG_DOCUMENT_AS_TEXT = 0; - - private String mExpectedResult; - private String mExpectedResultPath; - private String mActualResult; - private String mRelativePath; - private boolean mDidTimeOut; - private ResultCode mResultCode; - transient private Message mResultObtainedMsg; - - private boolean mDumpChildFramesAsText; - - transient private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_DOCUMENT_AS_TEXT) { - mActualResult = (String)msg.obj; - mResultObtainedMsg.sendToTarget(); - } - } - }; - - public TextResult(String relativePath) { - mRelativePath = relativePath; - } - - public void setDumpChildFramesAsText(boolean dumpChildFramesAsText) { - mDumpChildFramesAsText = dumpChildFramesAsText; - } - - /** - * Used to recreate the Result when received by the service. - * - * @param bundle - * bundle with data used to recreate the result - */ - public TextResult(Bundle bundle) { - mExpectedResult = bundle.getString("expectedTextualResult"); - mExpectedResultPath = bundle.getString("expectedTextualResultPath"); - mActualResult = bundle.getString("actualTextualResult"); - setAdditionalTextOutputString(bundle.getString("additionalTextOutputString")); - mRelativePath = bundle.getString("relativePath"); - mDidTimeOut = bundle.getBoolean("didTimeOut"); - } - - @Override - public void clearResults() { - super.clearResults(); - mExpectedResult = null; - mActualResult = null; - } - - @Override - public ResultCode getResultCode() { - if (mResultCode == null) { - mResultCode = resultsMatch() ? AbstractResult.ResultCode.RESULTS_MATCH - : AbstractResult.ResultCode.RESULTS_DIFFER; - } - return mResultCode; - } - - private boolean resultsMatch() { - assert mExpectedResult != null; - assert mActualResult != null; - // Trim leading and trailing empty lines, as other WebKit platforms do. - String leadingEmptyLines = "^\\n+"; - String trailingEmptyLines = "\\n+$"; - String trimmedExpectedResult = mExpectedResult.replaceFirst(leadingEmptyLines, "") - .replaceFirst(trailingEmptyLines, ""); - String trimmedActualResult = mActualResult.replaceFirst(leadingEmptyLines, "") - .replaceFirst(trailingEmptyLines, ""); - return trimmedExpectedResult.equals(trimmedActualResult); - } - - @Override - public boolean didCrash() { - return false; - } - - @Override - public boolean didTimeOut() { - return mDidTimeOut; - } - - @Override - public void setDidTimeOut() { - mDidTimeOut = true; - } - - @Override - public byte[] getActualImageResult() { - return null; - } - - @Override - public String getActualTextResult() { - String additionalTextResultString = getAdditionalTextOutputString(); - if (additionalTextResultString != null) { - return additionalTextResultString + mActualResult; - } - - return mActualResult; - } - - @Override - public void setExpectedImageResult(byte[] expectedResult) { - /** This method is not applicable to this type of result */ - } - - @Override - public void setExpectedImageResultPath(String relativePath) { - /** This method is not applicable to this type of result */ - } - - @Override - public String getExpectedImageResultPath() { - /** This method is not applicable to this type of result */ - return null; - } - - @Override - public void setExpectedTextResultPath(String relativePath) { - mExpectedResultPath = relativePath; - } - - @Override - public String getExpectedTextResultPath() { - return mExpectedResultPath; - } - - @Override - public void setExpectedTextResult(String expectedResult) { - // For text results, we use an empty string for the expected result when none is - // present, as other WebKit platforms do. - mExpectedResult = expectedResult == null ? "" : expectedResult; - } - - @Override - public String getDiffAsHtml() { - assert mExpectedResult != null; - assert mActualResult != null; - - StringBuilder html = new StringBuilder(); - html.append("

        [Expand] @@ -1366,7 +1333,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1404,7 +1371,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1497,7 +1464,7 @@ From class - String + String toString() @@ -1568,7 +1535,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
        getParticipants() @@ -1751,7 +1718,7 @@ From interface - Bundle + Bundle getAutoMatchCriteria() @@ -1769,6 +1736,26 @@ From interface + int + + getAutoMatchWaitEstimateSeconds() + +
        Retrieves the estimated wait time for automatching to finish for players who are not + automatched immediately, as measured from the time that the room entered the + automatching pool.
        + +
        + abstract + + + + long @@ -1778,14 +1765,14 @@ From interface -
        abstract - String + String getCreatorId() @@ -1794,14 +1781,14 @@ From interface -
        abstract - String + String getDescription() @@ -1810,7 +1797,7 @@ From interface -
        abstract @@ -1820,7 +1807,7 @@ From interface void - getDescription(CharArrayBuffer dataOut) + getDescription(CharArrayBuffer dataOut)
        Loads the room description into the given CharArrayBuffer.
        @@ -1828,17 +1815,17 @@ From interface -
        abstract - String + String - getParticipantId(String playerId) + getParticipantId(String playerId)
        Get the participant ID for a given player.
        @@ -1846,14 +1833,14 @@ From interface -
        abstract - ArrayList<String> + ArrayList<String> getParticipantIds() @@ -1862,7 +1849,7 @@ From interface -
        abstract @@ -1872,7 +1859,7 @@ From interface int - getParticipantStatus(String participantId) + getParticipantStatus(String participantId)
        Get the status of a participant in a room.
        @@ -1880,14 +1867,14 @@ From interface -
        abstract - String + String getRoomId() @@ -1896,7 +1883,7 @@ From interface -
        abstract @@ -1912,7 +1899,7 @@ From interface -
        abstract @@ -1973,7 +1960,7 @@ From interface public static final - RoomEntityCreator + Creator<RoomEntity> CREATOR @@ -2052,7 +2039,7 @@ From interface boolean equals - (Object obj) + (Object obj)
        @@ -2125,7 +2112,7 @@ From interface - Bundle + Bundle getAutoMatchCriteria () @@ -2150,6 +2137,43 @@ From interface
        + + +
        +

        + + public + + + + + int + + getAutoMatchWaitEstimateSeconds + () +

        +
        +
        + + + +
        +
        + +

        Retrieves the estimated wait time for automatching to finish for players who are not + automatched immediately, as measured from the time that the room entered the + automatching pool.

        +
        +
        Returns
        +
        • The estimated wait time in seconds, or -1 if the room is not + automatching or no estimate could be provided. +
        +
        + +
        +
        + +
        @@ -2189,7 +2213,7 @@ From interface - String + String getCreatorId () @@ -2218,7 +2242,7 @@ From interface - String + String getDescription () @@ -2250,7 +2274,7 @@ From interface void getDescription - (CharArrayBuffer dataOut) + (CharArrayBuffer dataOut)
        @@ -2286,10 +2310,10 @@ From interface - String + String getParticipantId - (String playerId) + (String playerId)
        @@ -2331,7 +2355,7 @@ From interface - ArrayList<String> + ArrayList<String> getParticipantIds () @@ -2363,7 +2387,7 @@ From interface int getParticipantStatus - (String participantId) + (String participantId)
        @@ -2405,7 +2429,7 @@ From interface - ArrayList<Participant> + ArrayList<Participant> getParticipants () @@ -2440,7 +2464,7 @@ From interface - String + String getRoomId () @@ -2592,7 +2616,7 @@ From interface - String + String toString () @@ -2624,7 +2648,7 @@ From interface void writeToParcel - (Parcel dest, int flags) + (Parcel dest, int flags)
        @@ -2645,113 +2669,6 @@ From interface -

        Protected Methods

        - - - - - -
        -

        - - protected - static - - - - ClassLoader - - getUnparcelClassLoader - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The ClassLoader to use for unparceling, if any. -
        -
        - -
        -
        - - - - -
        -

        - - protected - static - - - - Integer - - getUnparcelClientVersion - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • The client version to use for unparceling, if known. -
        -
        - -
        -
        - - - - -
        -

        - - protected - - - - - boolean - - shouldDowngrade - () -

        -
        -
        - - - -
        -
        - -

        -
        -
        Returns
        -
        • Whether or not this object has been downgraded to hand back to a client. -
        -
        - -
        -
        - - - diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomStatusUpdateListener.html b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomStatusUpdateListener.html index ab365b1683bf34efb0b3d66b95ce641ddbdf4198..fd02e45d29a263c8ea36d01568eb2db914cdf250 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomStatusUpdateListener.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomStatusUpdateListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + RoomStatusUpdateListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onP2PConnected(String participantId) + onP2PConnected(String participantId)
        Called when the client is successfully connected to a peer participant.
        @@ -806,7 +813,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onP2PDisconnected(String participantId) + onP2PDisconnected(String participantId)
        Called when client gets disconnected from a peer participant.
        @@ -824,7 +831,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onPeerDeclined(Room room, List<String> participantIds) + onPeerDeclined(Room room, List<String> participantIds)
        Called when one or more peers decline the invitation to a room.
        @@ -842,7 +849,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onPeerInvitedToRoom(Room room, List<String> participantIds) + onPeerInvitedToRoom(Room room, List<String> participantIds)
        Called when one or more peers are invited to a room.
        @@ -860,7 +867,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onPeerJoined(Room room, List<String> participantIds) + onPeerJoined(Room room, List<String> participantIds)
        Called when one or more peer participants join a room.
        @@ -878,7 +885,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onPeerLeft(Room room, List<String> participantIds) + onPeerLeft(Room room, List<String> participantIds)
        Called when one or more peer participant leave a room.
        @@ -896,7 +903,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onPeersConnected(Room room, List<String> participantIds) + onPeersConnected(Room room, List<String> participantIds)
        Called when one or more peer participants are connected to a room.
        @@ -914,7 +921,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onPeersDisconnected(Room room, List<String> participantIds) + onPeersDisconnected(Room room, List<String> participantIds)
        Called when one or more peer participants are disconnected from a room.
        @@ -1099,7 +1106,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onP2PConnected - (String participantId) + (String participantId)
        @@ -1138,7 +1145,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onP2PDisconnected - (String participantId) + (String participantId)
        @@ -1177,7 +1184,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPeerDeclined - (Room room, List<String> participantIds) + (Room room, List<String> participantIds)
        @@ -1221,7 +1228,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPeerInvitedToRoom - (Room room, List<String> participantIds) + (Room room, List<String> participantIds)
        @@ -1265,7 +1272,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPeerJoined - (Room room, List<String> participantIds) + (Room room, List<String> participantIds)
        @@ -1309,7 +1316,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPeerLeft - (Room room, List<String> participantIds) + (Room room, List<String> participantIds)
        @@ -1353,7 +1360,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPeersConnected - (Room room, List<String> participantIds) + (Room room, List<String> participantIds)
        @@ -1397,7 +1404,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPeersDisconnected - (Room room, List<String> participantIds) + (Room room, List<String> participantIds)
        diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomUpdateListener.html b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomUpdateListener.html index 2235af7e9e62199868d0a7d28601d027b5708b67..a28307f3458fccca86c1fb721256e9d10834cf2d 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomUpdateListener.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/RoomUpdateListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + RoomUpdateListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onLeftRoom(int statusCode, String roomId) + onLeftRoom(int statusCode, String roomId)
        Called when the client attempts to leaves the real-time room.
        @@ -928,7 +935,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onLeftRoom - (int statusCode, String roomId) + (int statusCode, String roomId)
        diff --git a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/package-summary.html b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/package-summary.html index 82f3aae5088f6b72fcd41d9444f08a78b6047c37..61f9d5cd186456cd201ff07259e3e4589ae03d68 100644 --- a/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/package-summary.html +++ b/docs/html/reference/com/google/android/gms/games/multiplayer/realtime/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + com.google.android.gms.games.multiplayer.realtime | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        StringString ERROR_MAIN_THREADGCM methods are blocking.The GCM register() and unregister() methods are + blocking.
        StringString ERROR_SERVICE_NOT_AVAILABLE The device can't read the response, or there was a 500/503 from the server that can be retried later.
        StringString MESSAGE_TYPE_DELETED Returned by getMessageType(Intent) to indicate that the server deleted some pending messages because they were collapsible.
        StringString MESSAGE_TYPE_MESSAGE Returned by getMessageType(Intent) to indicate a regular message.
        StringString MESSAGE_TYPE_SEND_ERROR Returned by getMessageType(Intent) to indicate a send error.
        - getInstance(Context context) + getInstance(Context context)
        Return the singleton instance of GCM.
        @@ -951,12 +1000,12 @@ Summary: - String + String
        - getMessageType(Intent intent) + getMessageType(Intent intent) -
        Return the message type.
        +
        Return the message type from an intent passed into a client app's broadcast receiver.
        - register(String... senderIds) + register(String... senderIds)
        Register the application for GCM and return the registration ID.
        @@ -990,9 +1039,9 @@ Summary: void
        - send(String to, String msgId, long timeToLive, Bundle data) + send(String to, String msgId, long timeToLive, Bundle data) -
        Send a "device to cloud" message.
        +
        Send an upstream ("device to cloud") message.
        - send(String to, String msgId, Bundle data) + send(String to, String msgId, Bundle data) -
        Send a "device to cloud" message.
        +
        Send an upstream ("device to cloud") message.
        clone() @@ -1094,7 +1143,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1187,7 +1236,7 @@ From class - String + String toString() @@ -1286,7 +1335,7 @@ From class public static final - String + String ERROR_MAIN_THREAD @@ -1298,8 +1347,8 @@ From class
        -

        GCM methods are blocking. You should not run them in the main thread or in broadcast - receivers. +

        The GCM register() and unregister() methods are + blocking. You should not run them in the main thread or in broadcast receivers.

        @@ -1325,7 +1374,7 @@ From class public static final - String + String ERROR_SERVICE_NOT_AVAILABLE @@ -1365,7 +1414,7 @@ From class public static final - String + String MESSAGE_TYPE_DELETED @@ -1404,7 +1453,7 @@ From class public static final - String + String MESSAGE_TYPE_MESSAGE @@ -1442,7 +1491,7 @@ From class public static final - String + String MESSAGE_TYPE_SEND_ERROR @@ -1573,7 +1622,7 @@ From class GoogleCloudMessaging getInstance - (Context context) + (Context context)
        @@ -1599,10 +1648,10 @@ From class - String + String getMessageType - (Intent intent) + (Intent intent)
        @@ -1612,20 +1661,25 @@ From class
        -

        Return the message type. Regular messages from the server have the type - GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE. - - The server may also send special messages. The possible types are: -

          -
        • MESSAGE_TYPE_MESSAGE—regular message from your server. -
        • MESSAGE_TYPE_DELETED—if some messages have been collapsed by GCM. -
        • MESSAGE_TYPE_SEND_ERROR—indicates errors sending one of the messages. +

          Return the message type from an intent passed into a client app's broadcast receiver. There + are two general categories of messages passed from the server: regular GCM messages, + and special GCM status messages. + + The possible types are: +

          - Additional types may be added later; you should ignore any type you don't handle.

          + You can use this method to filter based on message type. Since it is likely that GCM will + be extended in the future with new message types, just ignore any message types you're not + interested in, or that you don't recognize.

        Returns
        -
        • the message type or null if the intent is not a GCM intent +
          • The message type or null if the intent is not a GCM intent
        @@ -1643,10 +1697,10 @@ From class - String + String register - (String... senderIds) + (String... senderIds)
        @@ -1684,7 +1738,7 @@ From class
        Throws
        -
        IOException + IOException
        @@ -1707,7 +1761,7 @@ From class void send - (String to, String msgId, long timeToLive, Bundle data) + (String to, String msgId, long timeToLive, Bundle data)
        @@ -1717,11 +1771,13 @@ From class
        -

        Send a "device to cloud" message. +

        Send an upstream ("device to cloud") message. You can only use the upstream feature + if your GCM implementation uses the XMPP-based + Cloud Connection Server. The current limits for max storage time and number of outstanding messages per application are documented in the - GCM Dev Guide.

        + GCM Developers Guide.

        Parameters
        @@ -1747,8 +1803,7 @@ From class + be ignored.
        data key/value pairs to be sent. Values must be String, any other type will - be ignored. -
        @@ -1756,7 +1811,11 @@ From class
        Throws
        - + + +
        IOException + IllegalArgumentException +
        IOException
        @@ -1779,7 +1838,7 @@ From class void send - (String to, String msgId, Bundle data) + (String to, String msgId, Bundle data)
        @@ -1789,7 +1848,9 @@ From class
        -

        Send a "device to cloud" message. +

        Send an upstream ("device to cloud") message. You can only use the upstream feature + if your GCM implementation uses the XMPP-based + Cloud Connection Server. The message will be queued if we don't have an active connection for the max interval.

        @@ -1817,7 +1878,11 @@ From class
        Throws
        - + + +
        IOException + + IllegalArgumentException
        IOException
        @@ -1856,15 +1921,14 @@ From class You should rarely (if ever) need to call this method. Not only is it expensive in terms of resources, but it invalidates your registration ID, - which should never change unnecessarily. A better approach is to simply + which you should never change unnecessarily. A better approach is to simply have your server stop sending messages. Only use unregister if you want - your application to stop using GCM permanently, or you have a compelling - reason to recycle your registration ID.

        + to change your sender ID.

        Throws
        - diff --git a/docs/html/reference/com/google/android/gms/gcm/package-summary.html b/docs/html/reference/com/google/android/gms/gcm/package-summary.html index ee590df598adf77423768e722cead5fa6dc81614..0b4cb550fa6463ec01059e7858e6c3db6c8c0a64 100644 --- a/docs/html/reference/com/google/android/gms/gcm/package-summary.html +++ b/docs/html/reference/com/google/android/gms/gcm/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +com.google.android.gms.gcm | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        IOException + IOException if we can't connect to server to unregister.
        - +
        GoogleCloudMessaging

        Google Cloud Messaging for Android. 

        The class you use to write a GCM-enabled client application that runs on an Android device. 

        diff --git a/docs/html/reference/com/google/android/gms/location/ActivityRecognitionClient.html b/docs/html/reference/com/google/android/gms/location/ActivityRecognitionClient.html index b2fe29bad9faf0fa4a735c4d347914f424650297..56cc48ddac6d550d4d29978df5d9c88fc9e66471 100644 --- a/docs/html/reference/com/google/android/gms/location/ActivityRecognitionClient.html +++ b/docs/html/reference/com/google/android/gms/location/ActivityRecognitionClient.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + ActivityRecognitionClient | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - ActivityRecognitionClient(Context context, GooglePlayServicesClient.ConnectionCallbacks connectedListener, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + ActivityRecognitionClient(Context context, GooglePlayServicesClient.ConnectionCallbacks connectedListener, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) @@ -1021,7 +1028,7 @@ Summary: void - removeActivityUpdates(PendingIntent callbackIntent) + removeActivityUpdates(PendingIntent callbackIntent)
        Removes all activity updates for the specified PendingIntent.
        @@ -1039,7 +1046,7 @@ Summary: void
        - requestActivityUpdates(long detectionIntervalMillis, PendingIntent callbackIntent) + requestActivityUpdates(long detectionIntervalMillis, PendingIntent callbackIntent)
        Register for activity recognition updates.
        @@ -1105,7 +1112,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1143,7 +1150,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1236,7 +1243,7 @@ From class - String + String toString() @@ -1555,7 +1562,7 @@ From interface ActivityRecognitionClient - (Context context, GooglePlayServicesClient.ConnectionCallbacks connectedListener, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + (Context context, GooglePlayServicesClient.ConnectionCallbacks connectedListener, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener)
        @@ -1944,7 +1951,7 @@ From interface void removeActivityUpdates - (PendingIntent callbackIntent) + (PendingIntent callbackIntent)
        @@ -1972,7 +1979,7 @@ From interface
        Throws
        - @@ -1094,7 +1101,7 @@ Summary: - List<Person.Emails> + List<Person.Emails> - - - - - - + + + + + + + + + + + + + + - - - - - - + @@ -952,7 +959,7 @@ From class - Iterator<T> + Iterator<T> @@ -1042,7 +1049,7 @@ From class final - Class<?> + Class<?> - - - - - + @@ -704,11 +704,27 @@ onkeyup="return search_changed(event, false, '/')" /> + + + + + + + + + + + + - + diff --git a/docs/html/reference/gms-packages.html b/docs/html/reference/gms-packages.html index a1765952f82f6607617a897c98beea89d93ddafd..bafda1cda2a3b431983bad95b22dfdcfc1473de5 100644 --- a/docs/html/reference/gms-packages.html +++ b/docs/html/reference/gms-packages.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +Package Index | Android Developers @@ -304,6 +306,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -370,6 +373,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -502,24 +506,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -698,7 +705,7 @@ onkeyup="return search_changed(event, false, '/')" /> - + diff --git a/docs/html/reference/gms_lists.js b/docs/html/reference/gms_lists.js index 9d6258eeb1a366ef13ef8807bb44164672c451d4..27b16e0bba8c297e0095f014b469dc1604d24377 100644 --- a/docs/html/reference/gms_lists.js +++ b/docs/html/reference/gms_lists.js @@ -32,170 +32,174 @@ var GMS_DATA = [ { id:30, label:"com.google.android.gms.common.GooglePlayServicesUtil", link:"reference/com/google/android/gms/common/GooglePlayServicesUtil.html", type:"class", deprecated:"false" }, { id:31, label:"com.google.android.gms.common.Scopes", link:"reference/com/google/android/gms/common/Scopes.html", type:"class", deprecated:"false" }, { id:32, label:"com.google.android.gms.common.SignInButton", link:"reference/com/google/android/gms/common/SignInButton.html", type:"class", deprecated:"false" }, - { id:33, label:"com.google.android.gms.common.data", link:"reference/com/google/android/gms/common/data/package-summary.html", type:"package", deprecated:"false" }, - { id:34, label:"com.google.android.gms.common.data.DataBuffer", link:"reference/com/google/android/gms/common/data/DataBuffer.html", type:"class", deprecated:"false" }, - { id:35, label:"com.google.android.gms.common.data.DataBufferUtils", link:"reference/com/google/android/gms/common/data/DataBufferUtils.html", type:"class", deprecated:"false" }, - { id:36, label:"com.google.android.gms.common.data.Freezable", link:"reference/com/google/android/gms/common/data/Freezable.html", type:"class", deprecated:"false" }, - { id:37, label:"com.google.android.gms.common.images", link:"reference/com/google/android/gms/common/images/package-summary.html", type:"package", deprecated:"false" }, - { id:38, label:"com.google.android.gms.common.images.ImageManager", link:"reference/com/google/android/gms/common/images/ImageManager.html", type:"class", deprecated:"false" }, - { id:39, label:"com.google.android.gms.common.images.ImageManager.OnImageLoadedListener", link:"reference/com/google/android/gms/common/images/ImageManager.OnImageLoadedListener.html", type:"class", deprecated:"false" }, - { id:40, label:"com.google.android.gms.games", link:"reference/com/google/android/gms/games/package-summary.html", type:"package", deprecated:"false" }, - { id:41, label:"com.google.android.gms.games.Game", link:"reference/com/google/android/gms/games/Game.html", type:"class", deprecated:"false" }, - { id:42, label:"com.google.android.gms.games.GameBuffer", link:"reference/com/google/android/gms/games/GameBuffer.html", type:"class", deprecated:"false" }, - { id:43, label:"com.google.android.gms.games.GameEntity", link:"reference/com/google/android/gms/games/GameEntity.html", type:"class", deprecated:"false" }, - { id:44, label:"com.google.android.gms.games.GamesActivityResultCodes", link:"reference/com/google/android/gms/games/GamesActivityResultCodes.html", type:"class", deprecated:"false" }, - { id:45, label:"com.google.android.gms.games.GamesClient", link:"reference/com/google/android/gms/games/GamesClient.html", type:"class", deprecated:"false" }, - { id:46, label:"com.google.android.gms.games.GamesClient.Builder", link:"reference/com/google/android/gms/games/GamesClient.Builder.html", type:"class", deprecated:"false" }, - { id:47, label:"com.google.android.gms.games.OnGamesLoadedListener", link:"reference/com/google/android/gms/games/OnGamesLoadedListener.html", type:"class", deprecated:"false" }, - { id:48, label:"com.google.android.gms.games.OnPlayersLoadedListener", link:"reference/com/google/android/gms/games/OnPlayersLoadedListener.html", type:"class", deprecated:"false" }, - { id:49, label:"com.google.android.gms.games.OnSignOutCompleteListener", link:"reference/com/google/android/gms/games/OnSignOutCompleteListener.html", type:"class", deprecated:"false" }, - { id:50, label:"com.google.android.gms.games.PageDirection", link:"reference/com/google/android/gms/games/PageDirection.html", type:"class", deprecated:"false" }, - { id:51, label:"com.google.android.gms.games.Player", link:"reference/com/google/android/gms/games/Player.html", type:"class", deprecated:"false" }, - { id:52, label:"com.google.android.gms.games.PlayerBuffer", link:"reference/com/google/android/gms/games/PlayerBuffer.html", type:"class", deprecated:"false" }, - { id:53, label:"com.google.android.gms.games.PlayerEntity", link:"reference/com/google/android/gms/games/PlayerEntity.html", type:"class", deprecated:"false" }, - { id:54, label:"com.google.android.gms.games.RealTimeSocket", link:"reference/com/google/android/gms/games/RealTimeSocket.html", type:"class", deprecated:"false" }, - { id:55, label:"com.google.android.gms.games.achievement", link:"reference/com/google/android/gms/games/achievement/package-summary.html", type:"package", deprecated:"false" }, - { id:56, label:"com.google.android.gms.games.achievement.Achievement", link:"reference/com/google/android/gms/games/achievement/Achievement.html", type:"class", deprecated:"false" }, - { id:57, label:"com.google.android.gms.games.achievement.AchievementBuffer", link:"reference/com/google/android/gms/games/achievement/AchievementBuffer.html", type:"class", deprecated:"false" }, - { id:58, label:"com.google.android.gms.games.achievement.OnAchievementUpdatedListener", link:"reference/com/google/android/gms/games/achievement/OnAchievementUpdatedListener.html", type:"class", deprecated:"false" }, - { id:59, label:"com.google.android.gms.games.achievement.OnAchievementsLoadedListener", link:"reference/com/google/android/gms/games/achievement/OnAchievementsLoadedListener.html", type:"class", deprecated:"false" }, - { id:60, label:"com.google.android.gms.games.leaderboard", link:"reference/com/google/android/gms/games/leaderboard/package-summary.html", type:"package", deprecated:"false" }, - { id:61, label:"com.google.android.gms.games.leaderboard.Leaderboard", link:"reference/com/google/android/gms/games/leaderboard/Leaderboard.html", type:"class", deprecated:"false" }, - { id:62, label:"com.google.android.gms.games.leaderboard.LeaderboardBuffer", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardBuffer.html", type:"class", deprecated:"false" }, - { id:63, label:"com.google.android.gms.games.leaderboard.LeaderboardScore", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardScore.html", type:"class", deprecated:"false" }, - { id:64, label:"com.google.android.gms.games.leaderboard.LeaderboardScoreBuffer", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardScoreBuffer.html", type:"class", deprecated:"false" }, - { id:65, label:"com.google.android.gms.games.leaderboard.LeaderboardVariant", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardVariant.html", type:"class", deprecated:"false" }, - { id:66, label:"com.google.android.gms.games.leaderboard.OnLeaderboardMetadataLoadedListener", link:"reference/com/google/android/gms/games/leaderboard/OnLeaderboardMetadataLoadedListener.html", type:"class", deprecated:"false" }, - { id:67, label:"com.google.android.gms.games.leaderboard.OnLeaderboardScoresLoadedListener", link:"reference/com/google/android/gms/games/leaderboard/OnLeaderboardScoresLoadedListener.html", type:"class", deprecated:"false" }, - { id:68, label:"com.google.android.gms.games.leaderboard.OnScoreSubmittedListener", link:"reference/com/google/android/gms/games/leaderboard/OnScoreSubmittedListener.html", type:"class", deprecated:"false" }, - { id:69, label:"com.google.android.gms.games.leaderboard.SubmitScoreResult", link:"reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.html", type:"class", deprecated:"false" }, - { id:70, label:"com.google.android.gms.games.leaderboard.SubmitScoreResult.Result", link:"reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.Result.html", type:"class", deprecated:"false" }, - { id:71, label:"com.google.android.gms.games.multiplayer", link:"reference/com/google/android/gms/games/multiplayer/package-summary.html", type:"package", deprecated:"false" }, - { id:72, label:"com.google.android.gms.games.multiplayer.Invitation", link:"reference/com/google/android/gms/games/multiplayer/Invitation.html", type:"class", deprecated:"false" }, - { id:73, label:"com.google.android.gms.games.multiplayer.InvitationBuffer", link:"reference/com/google/android/gms/games/multiplayer/InvitationBuffer.html", type:"class", deprecated:"false" }, - { id:74, label:"com.google.android.gms.games.multiplayer.InvitationEntity", link:"reference/com/google/android/gms/games/multiplayer/InvitationEntity.html", type:"class", deprecated:"false" }, - { id:75, label:"com.google.android.gms.games.multiplayer.OnInvitationReceivedListener", link:"reference/com/google/android/gms/games/multiplayer/OnInvitationReceivedListener.html", type:"class", deprecated:"false" }, - { id:76, label:"com.google.android.gms.games.multiplayer.OnInvitationsLoadedListener", link:"reference/com/google/android/gms/games/multiplayer/OnInvitationsLoadedListener.html", type:"class", deprecated:"false" }, - { id:77, label:"com.google.android.gms.games.multiplayer.Participant", link:"reference/com/google/android/gms/games/multiplayer/Participant.html", type:"class", deprecated:"false" }, - { id:78, label:"com.google.android.gms.games.multiplayer.ParticipantBuffer", link:"reference/com/google/android/gms/games/multiplayer/ParticipantBuffer.html", type:"class", deprecated:"false" }, - { id:79, label:"com.google.android.gms.games.multiplayer.ParticipantEntity", link:"reference/com/google/android/gms/games/multiplayer/ParticipantEntity.html", type:"class", deprecated:"false" }, - { id:80, label:"com.google.android.gms.games.multiplayer.ParticipantUtils", link:"reference/com/google/android/gms/games/multiplayer/ParticipantUtils.html", type:"class", deprecated:"false" }, - { id:81, label:"com.google.android.gms.games.multiplayer.Participatable", link:"reference/com/google/android/gms/games/multiplayer/Participatable.html", type:"class", deprecated:"false" }, - { id:82, label:"com.google.android.gms.games.multiplayer.realtime", link:"reference/com/google/android/gms/games/multiplayer/realtime/package-summary.html", type:"package", deprecated:"false" }, - { id:83, label:"com.google.android.gms.games.multiplayer.realtime.RealTimeMessage", link:"reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessage.html", type:"class", deprecated:"false" }, - { id:84, label:"com.google.android.gms.games.multiplayer.realtime.RealTimeMessageReceivedListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessageReceivedListener.html", type:"class", deprecated:"false" }, - { id:85, label:"com.google.android.gms.games.multiplayer.realtime.RealTimeReliableMessageSentListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RealTimeReliableMessageSentListener.html", type:"class", deprecated:"false" }, - { id:86, label:"com.google.android.gms.games.multiplayer.realtime.Room", link:"reference/com/google/android/gms/games/multiplayer/realtime/Room.html", type:"class", deprecated:"false" }, - { id:87, label:"com.google.android.gms.games.multiplayer.realtime.RoomConfig", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.html", type:"class", deprecated:"false" }, - { id:88, label:"com.google.android.gms.games.multiplayer.realtime.RoomConfig.Builder", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.Builder.html", type:"class", deprecated:"false" }, - { id:89, label:"com.google.android.gms.games.multiplayer.realtime.RoomEntity", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomEntity.html", type:"class", deprecated:"false" }, - { id:90, label:"com.google.android.gms.games.multiplayer.realtime.RoomStatusUpdateListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomStatusUpdateListener.html", type:"class", deprecated:"false" }, - { id:91, label:"com.google.android.gms.games.multiplayer.realtime.RoomUpdateListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomUpdateListener.html", type:"class", deprecated:"false" }, - { id:92, label:"com.google.android.gms.gcm", link:"reference/com/google/android/gms/gcm/package-summary.html", type:"package", deprecated:"false" }, - { id:93, label:"com.google.android.gms.gcm.GoogleCloudMessaging", link:"reference/com/google/android/gms/gcm/GoogleCloudMessaging.html", type:"class", deprecated:"false" }, - { id:94, label:"com.google.android.gms.location", link:"reference/com/google/android/gms/location/package-summary.html", type:"package", deprecated:"false" }, - { id:95, label:"com.google.android.gms.location.ActivityRecognitionClient", link:"reference/com/google/android/gms/location/ActivityRecognitionClient.html", type:"class", deprecated:"false" }, - { id:96, label:"com.google.android.gms.location.ActivityRecognitionResult", link:"reference/com/google/android/gms/location/ActivityRecognitionResult.html", type:"class", deprecated:"false" }, - { id:97, label:"com.google.android.gms.location.DetectedActivity", link:"reference/com/google/android/gms/location/DetectedActivity.html", type:"class", deprecated:"false" }, - { id:98, label:"com.google.android.gms.location.Geofence", link:"reference/com/google/android/gms/location/Geofence.html", type:"class", deprecated:"false" }, - { id:99, label:"com.google.android.gms.location.Geofence.Builder", link:"reference/com/google/android/gms/location/Geofence.Builder.html", type:"class", deprecated:"false" }, - { id:100, label:"com.google.android.gms.location.LocationClient", link:"reference/com/google/android/gms/location/LocationClient.html", type:"class", deprecated:"false" }, - { id:101, label:"com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener", link:"reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html", type:"class", deprecated:"false" }, - { id:102, label:"com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener", link:"reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html", type:"class", deprecated:"false" }, - { id:103, label:"com.google.android.gms.location.LocationListener", link:"reference/com/google/android/gms/location/LocationListener.html", type:"class", deprecated:"false" }, - { id:104, label:"com.google.android.gms.location.LocationRequest", link:"reference/com/google/android/gms/location/LocationRequest.html", type:"class", deprecated:"false" }, - { id:105, label:"com.google.android.gms.location.LocationStatusCodes", link:"reference/com/google/android/gms/location/LocationStatusCodes.html", type:"class", deprecated:"false" }, - { id:106, label:"com.google.android.gms.maps", link:"reference/com/google/android/gms/maps/package-summary.html", type:"package", deprecated:"false" }, - { id:107, label:"com.google.android.gms.maps.CameraUpdate", link:"reference/com/google/android/gms/maps/CameraUpdate.html", type:"class", deprecated:"false" }, - { id:108, label:"com.google.android.gms.maps.CameraUpdateFactory", link:"reference/com/google/android/gms/maps/CameraUpdateFactory.html", type:"class", deprecated:"false" }, - { id:109, label:"com.google.android.gms.maps.GoogleMap", link:"reference/com/google/android/gms/maps/GoogleMap.html", type:"class", deprecated:"false" }, - { id:110, label:"com.google.android.gms.maps.GoogleMap.CancelableCallback", link:"reference/com/google/android/gms/maps/GoogleMap.CancelableCallback.html", type:"class", deprecated:"false" }, - { id:111, label:"com.google.android.gms.maps.GoogleMap.InfoWindowAdapter", link:"reference/com/google/android/gms/maps/GoogleMap.InfoWindowAdapter.html", type:"class", deprecated:"false" }, - { id:112, label:"com.google.android.gms.maps.GoogleMap.OnCameraChangeListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.html", type:"class", deprecated:"false" }, - { id:113, label:"com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnInfoWindowClickListener.html", type:"class", deprecated:"false" }, - { id:114, label:"com.google.android.gms.maps.GoogleMap.OnMapClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMapClickListener.html", type:"class", deprecated:"false" }, - { id:115, label:"com.google.android.gms.maps.GoogleMap.OnMapLongClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMapLongClickListener.html", type:"class", deprecated:"false" }, - { id:116, label:"com.google.android.gms.maps.GoogleMap.OnMarkerClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMarkerClickListener.html", type:"class", deprecated:"false" }, - { id:117, label:"com.google.android.gms.maps.GoogleMap.OnMarkerDragListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMarkerDragListener.html", type:"class", deprecated:"false" }, - { id:118, label:"com.google.android.gms.maps.GoogleMap.OnMyLocationButtonClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMyLocationButtonClickListener.html", type:"class", deprecated:"false" }, - { id:119, label:"com.google.android.gms.maps.GoogleMap.OnMyLocationChangeListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMyLocationChangeListener.html", type:"class", deprecated:"true" }, - { id:120, label:"com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback", link:"reference/com/google/android/gms/maps/GoogleMap.SnapshotReadyCallback.html", type:"class", deprecated:"false" }, - { id:121, label:"com.google.android.gms.maps.GoogleMapOptions", link:"reference/com/google/android/gms/maps/GoogleMapOptions.html", type:"class", deprecated:"false" }, - { id:122, label:"com.google.android.gms.maps.LocationSource", link:"reference/com/google/android/gms/maps/LocationSource.html", type:"class", deprecated:"false" }, - { id:123, label:"com.google.android.gms.maps.LocationSource.OnLocationChangedListener", link:"reference/com/google/android/gms/maps/LocationSource.OnLocationChangedListener.html", type:"class", deprecated:"false" }, - { id:124, label:"com.google.android.gms.maps.MapFragment", link:"reference/com/google/android/gms/maps/MapFragment.html", type:"class", deprecated:"false" }, - { id:125, label:"com.google.android.gms.maps.MapView", link:"reference/com/google/android/gms/maps/MapView.html", type:"class", deprecated:"false" }, - { id:126, label:"com.google.android.gms.maps.MapsInitializer", link:"reference/com/google/android/gms/maps/MapsInitializer.html", type:"class", deprecated:"false" }, - { id:127, label:"com.google.android.gms.maps.Projection", link:"reference/com/google/android/gms/maps/Projection.html", type:"class", deprecated:"false" }, - { id:128, label:"com.google.android.gms.maps.SupportMapFragment", link:"reference/com/google/android/gms/maps/SupportMapFragment.html", type:"class", deprecated:"false" }, - { id:129, label:"com.google.android.gms.maps.UiSettings", link:"reference/com/google/android/gms/maps/UiSettings.html", type:"class", deprecated:"false" }, - { id:130, label:"com.google.android.gms.maps.model", link:"reference/com/google/android/gms/maps/model/package-summary.html", type:"package", deprecated:"false" }, - { id:131, label:"com.google.android.gms.maps.model.BitmapDescriptor", link:"reference/com/google/android/gms/maps/model/BitmapDescriptor.html", type:"class", deprecated:"false" }, - { id:132, label:"com.google.android.gms.maps.model.BitmapDescriptorFactory", link:"reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html", type:"class", deprecated:"false" }, - { id:133, label:"com.google.android.gms.maps.model.CameraPosition", link:"reference/com/google/android/gms/maps/model/CameraPosition.html", type:"class", deprecated:"false" }, - { id:134, label:"com.google.android.gms.maps.model.CameraPosition.Builder", link:"reference/com/google/android/gms/maps/model/CameraPosition.Builder.html", type:"class", deprecated:"false" }, - { id:135, label:"com.google.android.gms.maps.model.Circle", link:"reference/com/google/android/gms/maps/model/Circle.html", type:"class", deprecated:"false" }, - { id:136, label:"com.google.android.gms.maps.model.CircleOptions", link:"reference/com/google/android/gms/maps/model/CircleOptions.html", type:"class", deprecated:"false" }, - { id:137, label:"com.google.android.gms.maps.model.GroundOverlay", link:"reference/com/google/android/gms/maps/model/GroundOverlay.html", type:"class", deprecated:"false" }, - { id:138, label:"com.google.android.gms.maps.model.GroundOverlayOptions", link:"reference/com/google/android/gms/maps/model/GroundOverlayOptions.html", type:"class", deprecated:"false" }, - { id:139, label:"com.google.android.gms.maps.model.LatLng", link:"reference/com/google/android/gms/maps/model/LatLng.html", type:"class", deprecated:"false" }, - { id:140, label:"com.google.android.gms.maps.model.LatLngBounds", link:"reference/com/google/android/gms/maps/model/LatLngBounds.html", type:"class", deprecated:"false" }, - { id:141, label:"com.google.android.gms.maps.model.LatLngBounds.Builder", link:"reference/com/google/android/gms/maps/model/LatLngBounds.Builder.html", type:"class", deprecated:"false" }, - { id:142, label:"com.google.android.gms.maps.model.Marker", link:"reference/com/google/android/gms/maps/model/Marker.html", type:"class", deprecated:"false" }, - { id:143, label:"com.google.android.gms.maps.model.MarkerOptions", link:"reference/com/google/android/gms/maps/model/MarkerOptions.html", type:"class", deprecated:"false" }, - { id:144, label:"com.google.android.gms.maps.model.Polygon", link:"reference/com/google/android/gms/maps/model/Polygon.html", type:"class", deprecated:"false" }, - { id:145, label:"com.google.android.gms.maps.model.PolygonOptions", link:"reference/com/google/android/gms/maps/model/PolygonOptions.html", type:"class", deprecated:"false" }, - { id:146, label:"com.google.android.gms.maps.model.Polyline", link:"reference/com/google/android/gms/maps/model/Polyline.html", type:"class", deprecated:"false" }, - { id:147, label:"com.google.android.gms.maps.model.PolylineOptions", link:"reference/com/google/android/gms/maps/model/PolylineOptions.html", type:"class", deprecated:"false" }, - { id:148, label:"com.google.android.gms.maps.model.RuntimeRemoteException", link:"reference/com/google/android/gms/maps/model/RuntimeRemoteException.html", type:"class", deprecated:"false" }, - { id:149, label:"com.google.android.gms.maps.model.Tile", link:"reference/com/google/android/gms/maps/model/Tile.html", type:"class", deprecated:"false" }, - { id:150, label:"com.google.android.gms.maps.model.TileOverlay", link:"reference/com/google/android/gms/maps/model/TileOverlay.html", type:"class", deprecated:"false" }, - { id:151, label:"com.google.android.gms.maps.model.TileOverlayOptions", link:"reference/com/google/android/gms/maps/model/TileOverlayOptions.html", type:"class", deprecated:"false" }, - { id:152, label:"com.google.android.gms.maps.model.TileProvider", link:"reference/com/google/android/gms/maps/model/TileProvider.html", type:"class", deprecated:"false" }, - { id:153, label:"com.google.android.gms.maps.model.UrlTileProvider", link:"reference/com/google/android/gms/maps/model/UrlTileProvider.html", type:"class", deprecated:"false" }, - { id:154, label:"com.google.android.gms.maps.model.VisibleRegion", link:"reference/com/google/android/gms/maps/model/VisibleRegion.html", type:"class", deprecated:"false" }, - { id:155, label:"com.google.android.gms.panorama", link:"reference/com/google/android/gms/panorama/package-summary.html", type:"package", deprecated:"false" }, - { id:156, label:"com.google.android.gms.panorama.PanoramaClient", link:"reference/com/google/android/gms/panorama/PanoramaClient.html", type:"class", deprecated:"false" }, - { id:157, label:"com.google.android.gms.panorama.PanoramaClient.OnPanoramaInfoLoadedListener", link:"reference/com/google/android/gms/panorama/PanoramaClient.OnPanoramaInfoLoadedListener.html", type:"class", deprecated:"false" }, - { id:158, label:"com.google.android.gms.plus", link:"reference/com/google/android/gms/plus/package-summary.html", type:"package", deprecated:"false" }, - { id:159, label:"com.google.android.gms.plus.PlusClient", link:"reference/com/google/android/gms/plus/PlusClient.html", type:"class", deprecated:"false" }, - { id:160, label:"com.google.android.gms.plus.PlusClient.Builder", link:"reference/com/google/android/gms/plus/PlusClient.Builder.html", type:"class", deprecated:"false" }, - { id:161, label:"com.google.android.gms.plus.PlusClient.OnAccessRevokedListener", link:"reference/com/google/android/gms/plus/PlusClient.OnAccessRevokedListener.html", type:"class", deprecated:"false" }, - { id:162, label:"com.google.android.gms.plus.PlusClient.OnMomentsLoadedListener", link:"reference/com/google/android/gms/plus/PlusClient.OnMomentsLoadedListener.html", type:"class", deprecated:"false" }, - { id:163, label:"com.google.android.gms.plus.PlusClient.OnPeopleLoadedListener", link:"reference/com/google/android/gms/plus/PlusClient.OnPeopleLoadedListener.html", type:"class", deprecated:"false" }, - { id:164, label:"com.google.android.gms.plus.PlusClient.OnPersonLoadedListener", link:"reference/com/google/android/gms/plus/PlusClient.OnPersonLoadedListener.html", type:"class", deprecated:"true" }, - { id:165, label:"com.google.android.gms.plus.PlusClient.OrderBy", link:"reference/com/google/android/gms/plus/PlusClient.OrderBy.html", type:"class", deprecated:"false" }, - { id:166, label:"com.google.android.gms.plus.PlusOneButton", link:"reference/com/google/android/gms/plus/PlusOneButton.html", type:"class", deprecated:"false" }, - { id:167, label:"com.google.android.gms.plus.PlusOneButton.OnPlusOneClickListener", link:"reference/com/google/android/gms/plus/PlusOneButton.OnPlusOneClickListener.html", type:"class", deprecated:"false" }, - { id:168, label:"com.google.android.gms.plus.PlusShare", link:"reference/com/google/android/gms/plus/PlusShare.html", type:"class", deprecated:"false" }, - { id:169, label:"com.google.android.gms.plus.PlusShare.Builder", link:"reference/com/google/android/gms/plus/PlusShare.Builder.html", type:"class", deprecated:"false" }, - { id:170, label:"com.google.android.gms.plus.model.moments", link:"reference/com/google/android/gms/plus/model/moments/package-summary.html", type:"package", deprecated:"false" }, - { id:171, label:"com.google.android.gms.plus.model.moments.ItemScope", link:"reference/com/google/android/gms/plus/model/moments/ItemScope.html", type:"class", deprecated:"false" }, - { id:172, label:"com.google.android.gms.plus.model.moments.ItemScope.Builder", link:"reference/com/google/android/gms/plus/model/moments/ItemScope.Builder.html", type:"class", deprecated:"false" }, - { id:173, label:"com.google.android.gms.plus.model.moments.Moment", link:"reference/com/google/android/gms/plus/model/moments/Moment.html", type:"class", deprecated:"false" }, - { id:174, label:"com.google.android.gms.plus.model.moments.Moment.Builder", link:"reference/com/google/android/gms/plus/model/moments/Moment.Builder.html", type:"class", deprecated:"false" }, - { id:175, label:"com.google.android.gms.plus.model.moments.MomentBuffer", link:"reference/com/google/android/gms/plus/model/moments/MomentBuffer.html", type:"class", deprecated:"false" }, - { id:176, label:"com.google.android.gms.plus.model.people", link:"reference/com/google/android/gms/plus/model/people/package-summary.html", type:"package", deprecated:"false" }, - { id:177, label:"com.google.android.gms.plus.model.people.Person", link:"reference/com/google/android/gms/plus/model/people/Person.html", type:"class", deprecated:"false" }, - { id:178, label:"com.google.android.gms.plus.model.people.Person.AgeRange", link:"reference/com/google/android/gms/plus/model/people/Person.AgeRange.html", type:"class", deprecated:"false" }, - { id:179, label:"com.google.android.gms.plus.model.people.Person.Collection", link:"reference/com/google/android/gms/plus/model/people/Person.Collection.html", type:"class", deprecated:"true" }, - { id:180, label:"com.google.android.gms.plus.model.people.Person.Cover", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.html", type:"class", deprecated:"false" }, - { id:181, label:"com.google.android.gms.plus.model.people.Person.Cover.CoverInfo", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.CoverInfo.html", type:"class", deprecated:"false" }, - { id:182, label:"com.google.android.gms.plus.model.people.Person.Cover.CoverPhoto", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.CoverPhoto.html", type:"class", deprecated:"false" }, - { id:183, label:"com.google.android.gms.plus.model.people.Person.Cover.Layout", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.Layout.html", type:"class", deprecated:"false" }, - { id:184, label:"com.google.android.gms.plus.model.people.Person.Emails", link:"reference/com/google/android/gms/plus/model/people/Person.Emails.html", type:"class", deprecated:"false" }, - { id:185, label:"com.google.android.gms.plus.model.people.Person.Emails.Type", link:"reference/com/google/android/gms/plus/model/people/Person.Emails.Type.html", type:"class", deprecated:"false" }, - { id:186, label:"com.google.android.gms.plus.model.people.Person.Gender", link:"reference/com/google/android/gms/plus/model/people/Person.Gender.html", type:"class", deprecated:"false" }, - { id:187, label:"com.google.android.gms.plus.model.people.Person.Image", link:"reference/com/google/android/gms/plus/model/people/Person.Image.html", type:"class", deprecated:"false" }, - { id:188, label:"com.google.android.gms.plus.model.people.Person.Name", link:"reference/com/google/android/gms/plus/model/people/Person.Name.html", type:"class", deprecated:"false" }, - { id:189, label:"com.google.android.gms.plus.model.people.Person.ObjectType", link:"reference/com/google/android/gms/plus/model/people/Person.ObjectType.html", type:"class", deprecated:"false" }, - { id:190, label:"com.google.android.gms.plus.model.people.Person.OrderBy", link:"reference/com/google/android/gms/plus/model/people/Person.OrderBy.html", type:"class", deprecated:"true" }, - { id:191, label:"com.google.android.gms.plus.model.people.Person.Organizations", link:"reference/com/google/android/gms/plus/model/people/Person.Organizations.html", type:"class", deprecated:"false" }, - { id:192, label:"com.google.android.gms.plus.model.people.Person.Organizations.Type", link:"reference/com/google/android/gms/plus/model/people/Person.Organizations.Type.html", type:"class", deprecated:"false" }, - { id:193, label:"com.google.android.gms.plus.model.people.Person.PlacesLived", link:"reference/com/google/android/gms/plus/model/people/Person.PlacesLived.html", type:"class", deprecated:"false" }, - { id:194, label:"com.google.android.gms.plus.model.people.Person.RelationshipStatus", link:"reference/com/google/android/gms/plus/model/people/Person.RelationshipStatus.html", type:"class", deprecated:"false" }, - { id:195, label:"com.google.android.gms.plus.model.people.Person.Urls", link:"reference/com/google/android/gms/plus/model/people/Person.Urls.html", type:"class", deprecated:"false" }, - { id:196, label:"com.google.android.gms.plus.model.people.Person.Urls.Type", link:"reference/com/google/android/gms/plus/model/people/Person.Urls.Type.html", type:"class", deprecated:"false" }, - { id:197, label:"com.google.android.gms.plus.model.people.PersonBuffer", link:"reference/com/google/android/gms/plus/model/people/PersonBuffer.html", type:"class", deprecated:"false" } + { id:33, label:"com.google.android.gms.common.annotation", link:"reference/com/google/android/gms/common/annotation/package-summary.html", type:"package", deprecated:"false" }, + { id:34, label:"com.google.android.gms.common.annotation.KeepName", link:"reference/com/google/android/gms/common/annotation/KeepName.html", type:"class", deprecated:"false" }, + { id:35, label:"com.google.android.gms.common.data", link:"reference/com/google/android/gms/common/data/package-summary.html", type:"package", deprecated:"false" }, + { id:36, label:"com.google.android.gms.common.data.DataBuffer", link:"reference/com/google/android/gms/common/data/DataBuffer.html", type:"class", deprecated:"false" }, + { id:37, label:"com.google.android.gms.common.data.DataBufferUtils", link:"reference/com/google/android/gms/common/data/DataBufferUtils.html", type:"class", deprecated:"false" }, + { id:38, label:"com.google.android.gms.common.data.Freezable", link:"reference/com/google/android/gms/common/data/Freezable.html", type:"class", deprecated:"false" }, + { id:39, label:"com.google.android.gms.common.images", link:"reference/com/google/android/gms/common/images/package-summary.html", type:"package", deprecated:"false" }, + { id:40, label:"com.google.android.gms.common.images.ImageManager", link:"reference/com/google/android/gms/common/images/ImageManager.html", type:"class", deprecated:"false" }, + { id:41, label:"com.google.android.gms.common.images.ImageManager.OnImageLoadedListener", link:"reference/com/google/android/gms/common/images/ImageManager.OnImageLoadedListener.html", type:"class", deprecated:"false" }, + { id:42, label:"com.google.android.gms.games", link:"reference/com/google/android/gms/games/package-summary.html", type:"package", deprecated:"false" }, + { id:43, label:"com.google.android.gms.games.Game", link:"reference/com/google/android/gms/games/Game.html", type:"class", deprecated:"false" }, + { id:44, label:"com.google.android.gms.games.GameBuffer", link:"reference/com/google/android/gms/games/GameBuffer.html", type:"class", deprecated:"false" }, + { id:45, label:"com.google.android.gms.games.GameEntity", link:"reference/com/google/android/gms/games/GameEntity.html", type:"class", deprecated:"false" }, + { id:46, label:"com.google.android.gms.games.GamesActivityResultCodes", link:"reference/com/google/android/gms/games/GamesActivityResultCodes.html", type:"class", deprecated:"false" }, + { id:47, label:"com.google.android.gms.games.GamesClient", link:"reference/com/google/android/gms/games/GamesClient.html", type:"class", deprecated:"false" }, + { id:48, label:"com.google.android.gms.games.GamesClient.Builder", link:"reference/com/google/android/gms/games/GamesClient.Builder.html", type:"class", deprecated:"false" }, + { id:49, label:"com.google.android.gms.games.OnGamesLoadedListener", link:"reference/com/google/android/gms/games/OnGamesLoadedListener.html", type:"class", deprecated:"false" }, + { id:50, label:"com.google.android.gms.games.OnPlayersLoadedListener", link:"reference/com/google/android/gms/games/OnPlayersLoadedListener.html", type:"class", deprecated:"false" }, + { id:51, label:"com.google.android.gms.games.OnSignOutCompleteListener", link:"reference/com/google/android/gms/games/OnSignOutCompleteListener.html", type:"class", deprecated:"false" }, + { id:52, label:"com.google.android.gms.games.PageDirection", link:"reference/com/google/android/gms/games/PageDirection.html", type:"class", deprecated:"false" }, + { id:53, label:"com.google.android.gms.games.Player", link:"reference/com/google/android/gms/games/Player.html", type:"class", deprecated:"false" }, + { id:54, label:"com.google.android.gms.games.PlayerBuffer", link:"reference/com/google/android/gms/games/PlayerBuffer.html", type:"class", deprecated:"false" }, + { id:55, label:"com.google.android.gms.games.PlayerEntity", link:"reference/com/google/android/gms/games/PlayerEntity.html", type:"class", deprecated:"false" }, + { id:56, label:"com.google.android.gms.games.RealTimeSocket", link:"reference/com/google/android/gms/games/RealTimeSocket.html", type:"class", deprecated:"false" }, + { id:57, label:"com.google.android.gms.games.achievement", link:"reference/com/google/android/gms/games/achievement/package-summary.html", type:"package", deprecated:"false" }, + { id:58, label:"com.google.android.gms.games.achievement.Achievement", link:"reference/com/google/android/gms/games/achievement/Achievement.html", type:"class", deprecated:"false" }, + { id:59, label:"com.google.android.gms.games.achievement.AchievementBuffer", link:"reference/com/google/android/gms/games/achievement/AchievementBuffer.html", type:"class", deprecated:"false" }, + { id:60, label:"com.google.android.gms.games.achievement.OnAchievementUpdatedListener", link:"reference/com/google/android/gms/games/achievement/OnAchievementUpdatedListener.html", type:"class", deprecated:"false" }, + { id:61, label:"com.google.android.gms.games.achievement.OnAchievementsLoadedListener", link:"reference/com/google/android/gms/games/achievement/OnAchievementsLoadedListener.html", type:"class", deprecated:"false" }, + { id:62, label:"com.google.android.gms.games.leaderboard", link:"reference/com/google/android/gms/games/leaderboard/package-summary.html", type:"package", deprecated:"false" }, + { id:63, label:"com.google.android.gms.games.leaderboard.Leaderboard", link:"reference/com/google/android/gms/games/leaderboard/Leaderboard.html", type:"class", deprecated:"false" }, + { id:64, label:"com.google.android.gms.games.leaderboard.LeaderboardBuffer", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardBuffer.html", type:"class", deprecated:"false" }, + { id:65, label:"com.google.android.gms.games.leaderboard.LeaderboardScore", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardScore.html", type:"class", deprecated:"false" }, + { id:66, label:"com.google.android.gms.games.leaderboard.LeaderboardScoreBuffer", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardScoreBuffer.html", type:"class", deprecated:"false" }, + { id:67, label:"com.google.android.gms.games.leaderboard.LeaderboardVariant", link:"reference/com/google/android/gms/games/leaderboard/LeaderboardVariant.html", type:"class", deprecated:"false" }, + { id:68, label:"com.google.android.gms.games.leaderboard.OnLeaderboardMetadataLoadedListener", link:"reference/com/google/android/gms/games/leaderboard/OnLeaderboardMetadataLoadedListener.html", type:"class", deprecated:"false" }, + { id:69, label:"com.google.android.gms.games.leaderboard.OnLeaderboardScoresLoadedListener", link:"reference/com/google/android/gms/games/leaderboard/OnLeaderboardScoresLoadedListener.html", type:"class", deprecated:"false" }, + { id:70, label:"com.google.android.gms.games.leaderboard.OnScoreSubmittedListener", link:"reference/com/google/android/gms/games/leaderboard/OnScoreSubmittedListener.html", type:"class", deprecated:"false" }, + { id:71, label:"com.google.android.gms.games.leaderboard.SubmitScoreResult", link:"reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.html", type:"class", deprecated:"false" }, + { id:72, label:"com.google.android.gms.games.leaderboard.SubmitScoreResult.Result", link:"reference/com/google/android/gms/games/leaderboard/SubmitScoreResult.Result.html", type:"class", deprecated:"false" }, + { id:73, label:"com.google.android.gms.games.multiplayer", link:"reference/com/google/android/gms/games/multiplayer/package-summary.html", type:"package", deprecated:"false" }, + { id:74, label:"com.google.android.gms.games.multiplayer.Invitation", link:"reference/com/google/android/gms/games/multiplayer/Invitation.html", type:"class", deprecated:"false" }, + { id:75, label:"com.google.android.gms.games.multiplayer.InvitationBuffer", link:"reference/com/google/android/gms/games/multiplayer/InvitationBuffer.html", type:"class", deprecated:"false" }, + { id:76, label:"com.google.android.gms.games.multiplayer.InvitationEntity", link:"reference/com/google/android/gms/games/multiplayer/InvitationEntity.html", type:"class", deprecated:"false" }, + { id:77, label:"com.google.android.gms.games.multiplayer.OnInvitationReceivedListener", link:"reference/com/google/android/gms/games/multiplayer/OnInvitationReceivedListener.html", type:"class", deprecated:"false" }, + { id:78, label:"com.google.android.gms.games.multiplayer.OnInvitationsLoadedListener", link:"reference/com/google/android/gms/games/multiplayer/OnInvitationsLoadedListener.html", type:"class", deprecated:"false" }, + { id:79, label:"com.google.android.gms.games.multiplayer.Participant", link:"reference/com/google/android/gms/games/multiplayer/Participant.html", type:"class", deprecated:"false" }, + { id:80, label:"com.google.android.gms.games.multiplayer.ParticipantBuffer", link:"reference/com/google/android/gms/games/multiplayer/ParticipantBuffer.html", type:"class", deprecated:"false" }, + { id:81, label:"com.google.android.gms.games.multiplayer.ParticipantEntity", link:"reference/com/google/android/gms/games/multiplayer/ParticipantEntity.html", type:"class", deprecated:"false" }, + { id:82, label:"com.google.android.gms.games.multiplayer.ParticipantUtils", link:"reference/com/google/android/gms/games/multiplayer/ParticipantUtils.html", type:"class", deprecated:"false" }, + { id:83, label:"com.google.android.gms.games.multiplayer.Participatable", link:"reference/com/google/android/gms/games/multiplayer/Participatable.html", type:"class", deprecated:"false" }, + { id:84, label:"com.google.android.gms.games.multiplayer.realtime", link:"reference/com/google/android/gms/games/multiplayer/realtime/package-summary.html", type:"package", deprecated:"false" }, + { id:85, label:"com.google.android.gms.games.multiplayer.realtime.RealTimeMessage", link:"reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessage.html", type:"class", deprecated:"false" }, + { id:86, label:"com.google.android.gms.games.multiplayer.realtime.RealTimeMessageReceivedListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RealTimeMessageReceivedListener.html", type:"class", deprecated:"false" }, + { id:87, label:"com.google.android.gms.games.multiplayer.realtime.RealTimeReliableMessageSentListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RealTimeReliableMessageSentListener.html", type:"class", deprecated:"false" }, + { id:88, label:"com.google.android.gms.games.multiplayer.realtime.Room", link:"reference/com/google/android/gms/games/multiplayer/realtime/Room.html", type:"class", deprecated:"false" }, + { id:89, label:"com.google.android.gms.games.multiplayer.realtime.RoomConfig", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.html", type:"class", deprecated:"false" }, + { id:90, label:"com.google.android.gms.games.multiplayer.realtime.RoomConfig.Builder", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomConfig.Builder.html", type:"class", deprecated:"false" }, + { id:91, label:"com.google.android.gms.games.multiplayer.realtime.RoomEntity", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomEntity.html", type:"class", deprecated:"false" }, + { id:92, label:"com.google.android.gms.games.multiplayer.realtime.RoomStatusUpdateListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomStatusUpdateListener.html", type:"class", deprecated:"false" }, + { id:93, label:"com.google.android.gms.games.multiplayer.realtime.RoomUpdateListener", link:"reference/com/google/android/gms/games/multiplayer/realtime/RoomUpdateListener.html", type:"class", deprecated:"false" }, + { id:94, label:"com.google.android.gms.gcm", link:"reference/com/google/android/gms/gcm/package-summary.html", type:"package", deprecated:"false" }, + { id:95, label:"com.google.android.gms.gcm.GoogleCloudMessaging", link:"reference/com/google/android/gms/gcm/GoogleCloudMessaging.html", type:"class", deprecated:"false" }, + { id:96, label:"com.google.android.gms.location", link:"reference/com/google/android/gms/location/package-summary.html", type:"package", deprecated:"false" }, + { id:97, label:"com.google.android.gms.location.ActivityRecognitionClient", link:"reference/com/google/android/gms/location/ActivityRecognitionClient.html", type:"class", deprecated:"false" }, + { id:98, label:"com.google.android.gms.location.ActivityRecognitionResult", link:"reference/com/google/android/gms/location/ActivityRecognitionResult.html", type:"class", deprecated:"false" }, + { id:99, label:"com.google.android.gms.location.DetectedActivity", link:"reference/com/google/android/gms/location/DetectedActivity.html", type:"class", deprecated:"false" }, + { id:100, label:"com.google.android.gms.location.Geofence", link:"reference/com/google/android/gms/location/Geofence.html", type:"class", deprecated:"false" }, + { id:101, label:"com.google.android.gms.location.Geofence.Builder", link:"reference/com/google/android/gms/location/Geofence.Builder.html", type:"class", deprecated:"false" }, + { id:102, label:"com.google.android.gms.location.LocationClient", link:"reference/com/google/android/gms/location/LocationClient.html", type:"class", deprecated:"false" }, + { id:103, label:"com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener", link:"reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html", type:"class", deprecated:"false" }, + { id:104, label:"com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener", link:"reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html", type:"class", deprecated:"false" }, + { id:105, label:"com.google.android.gms.location.LocationListener", link:"reference/com/google/android/gms/location/LocationListener.html", type:"class", deprecated:"false" }, + { id:106, label:"com.google.android.gms.location.LocationRequest", link:"reference/com/google/android/gms/location/LocationRequest.html", type:"class", deprecated:"false" }, + { id:107, label:"com.google.android.gms.location.LocationStatusCodes", link:"reference/com/google/android/gms/location/LocationStatusCodes.html", type:"class", deprecated:"false" }, + { id:108, label:"com.google.android.gms.maps", link:"reference/com/google/android/gms/maps/package-summary.html", type:"package", deprecated:"false" }, + { id:109, label:"com.google.android.gms.maps.CameraUpdate", link:"reference/com/google/android/gms/maps/CameraUpdate.html", type:"class", deprecated:"false" }, + { id:110, label:"com.google.android.gms.maps.CameraUpdateFactory", link:"reference/com/google/android/gms/maps/CameraUpdateFactory.html", type:"class", deprecated:"false" }, + { id:111, label:"com.google.android.gms.maps.GoogleMap", link:"reference/com/google/android/gms/maps/GoogleMap.html", type:"class", deprecated:"false" }, + { id:112, label:"com.google.android.gms.maps.GoogleMap.CancelableCallback", link:"reference/com/google/android/gms/maps/GoogleMap.CancelableCallback.html", type:"class", deprecated:"false" }, + { id:113, label:"com.google.android.gms.maps.GoogleMap.InfoWindowAdapter", link:"reference/com/google/android/gms/maps/GoogleMap.InfoWindowAdapter.html", type:"class", deprecated:"false" }, + { id:114, label:"com.google.android.gms.maps.GoogleMap.OnCameraChangeListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.html", type:"class", deprecated:"false" }, + { id:115, label:"com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnInfoWindowClickListener.html", type:"class", deprecated:"false" }, + { id:116, label:"com.google.android.gms.maps.GoogleMap.OnMapClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMapClickListener.html", type:"class", deprecated:"false" }, + { id:117, label:"com.google.android.gms.maps.GoogleMap.OnMapLongClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMapLongClickListener.html", type:"class", deprecated:"false" }, + { id:118, label:"com.google.android.gms.maps.GoogleMap.OnMarkerClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMarkerClickListener.html", type:"class", deprecated:"false" }, + { id:119, label:"com.google.android.gms.maps.GoogleMap.OnMarkerDragListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMarkerDragListener.html", type:"class", deprecated:"false" }, + { id:120, label:"com.google.android.gms.maps.GoogleMap.OnMyLocationButtonClickListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMyLocationButtonClickListener.html", type:"class", deprecated:"false" }, + { id:121, label:"com.google.android.gms.maps.GoogleMap.OnMyLocationChangeListener", link:"reference/com/google/android/gms/maps/GoogleMap.OnMyLocationChangeListener.html", type:"class", deprecated:"true" }, + { id:122, label:"com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback", link:"reference/com/google/android/gms/maps/GoogleMap.SnapshotReadyCallback.html", type:"class", deprecated:"false" }, + { id:123, label:"com.google.android.gms.maps.GoogleMapOptions", link:"reference/com/google/android/gms/maps/GoogleMapOptions.html", type:"class", deprecated:"false" }, + { id:124, label:"com.google.android.gms.maps.LocationSource", link:"reference/com/google/android/gms/maps/LocationSource.html", type:"class", deprecated:"false" }, + { id:125, label:"com.google.android.gms.maps.LocationSource.OnLocationChangedListener", link:"reference/com/google/android/gms/maps/LocationSource.OnLocationChangedListener.html", type:"class", deprecated:"false" }, + { id:126, label:"com.google.android.gms.maps.MapFragment", link:"reference/com/google/android/gms/maps/MapFragment.html", type:"class", deprecated:"false" }, + { id:127, label:"com.google.android.gms.maps.MapView", link:"reference/com/google/android/gms/maps/MapView.html", type:"class", deprecated:"false" }, + { id:128, label:"com.google.android.gms.maps.MapsInitializer", link:"reference/com/google/android/gms/maps/MapsInitializer.html", type:"class", deprecated:"false" }, + { id:129, label:"com.google.android.gms.maps.Projection", link:"reference/com/google/android/gms/maps/Projection.html", type:"class", deprecated:"false" }, + { id:130, label:"com.google.android.gms.maps.SupportMapFragment", link:"reference/com/google/android/gms/maps/SupportMapFragment.html", type:"class", deprecated:"false" }, + { id:131, label:"com.google.android.gms.maps.UiSettings", link:"reference/com/google/android/gms/maps/UiSettings.html", type:"class", deprecated:"false" }, + { id:132, label:"com.google.android.gms.maps.model", link:"reference/com/google/android/gms/maps/model/package-summary.html", type:"package", deprecated:"false" }, + { id:133, label:"com.google.android.gms.maps.model.BitmapDescriptor", link:"reference/com/google/android/gms/maps/model/BitmapDescriptor.html", type:"class", deprecated:"false" }, + { id:134, label:"com.google.android.gms.maps.model.BitmapDescriptorFactory", link:"reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html", type:"class", deprecated:"false" }, + { id:135, label:"com.google.android.gms.maps.model.CameraPosition", link:"reference/com/google/android/gms/maps/model/CameraPosition.html", type:"class", deprecated:"false" }, + { id:136, label:"com.google.android.gms.maps.model.CameraPosition.Builder", link:"reference/com/google/android/gms/maps/model/CameraPosition.Builder.html", type:"class", deprecated:"false" }, + { id:137, label:"com.google.android.gms.maps.model.Circle", link:"reference/com/google/android/gms/maps/model/Circle.html", type:"class", deprecated:"false" }, + { id:138, label:"com.google.android.gms.maps.model.CircleOptions", link:"reference/com/google/android/gms/maps/model/CircleOptions.html", type:"class", deprecated:"false" }, + { id:139, label:"com.google.android.gms.maps.model.GroundOverlay", link:"reference/com/google/android/gms/maps/model/GroundOverlay.html", type:"class", deprecated:"false" }, + { id:140, label:"com.google.android.gms.maps.model.GroundOverlayOptions", link:"reference/com/google/android/gms/maps/model/GroundOverlayOptions.html", type:"class", deprecated:"false" }, + { id:141, label:"com.google.android.gms.maps.model.LatLng", link:"reference/com/google/android/gms/maps/model/LatLng.html", type:"class", deprecated:"false" }, + { id:142, label:"com.google.android.gms.maps.model.LatLngBounds", link:"reference/com/google/android/gms/maps/model/LatLngBounds.html", type:"class", deprecated:"false" }, + { id:143, label:"com.google.android.gms.maps.model.LatLngBounds.Builder", link:"reference/com/google/android/gms/maps/model/LatLngBounds.Builder.html", type:"class", deprecated:"false" }, + { id:144, label:"com.google.android.gms.maps.model.Marker", link:"reference/com/google/android/gms/maps/model/Marker.html", type:"class", deprecated:"false" }, + { id:145, label:"com.google.android.gms.maps.model.MarkerOptions", link:"reference/com/google/android/gms/maps/model/MarkerOptions.html", type:"class", deprecated:"false" }, + { id:146, label:"com.google.android.gms.maps.model.Polygon", link:"reference/com/google/android/gms/maps/model/Polygon.html", type:"class", deprecated:"false" }, + { id:147, label:"com.google.android.gms.maps.model.PolygonOptions", link:"reference/com/google/android/gms/maps/model/PolygonOptions.html", type:"class", deprecated:"false" }, + { id:148, label:"com.google.android.gms.maps.model.Polyline", link:"reference/com/google/android/gms/maps/model/Polyline.html", type:"class", deprecated:"false" }, + { id:149, label:"com.google.android.gms.maps.model.PolylineOptions", link:"reference/com/google/android/gms/maps/model/PolylineOptions.html", type:"class", deprecated:"false" }, + { id:150, label:"com.google.android.gms.maps.model.RuntimeRemoteException", link:"reference/com/google/android/gms/maps/model/RuntimeRemoteException.html", type:"class", deprecated:"false" }, + { id:151, label:"com.google.android.gms.maps.model.Tile", link:"reference/com/google/android/gms/maps/model/Tile.html", type:"class", deprecated:"false" }, + { id:152, label:"com.google.android.gms.maps.model.TileOverlay", link:"reference/com/google/android/gms/maps/model/TileOverlay.html", type:"class", deprecated:"false" }, + { id:153, label:"com.google.android.gms.maps.model.TileOverlayOptions", link:"reference/com/google/android/gms/maps/model/TileOverlayOptions.html", type:"class", deprecated:"false" }, + { id:154, label:"com.google.android.gms.maps.model.TileProvider", link:"reference/com/google/android/gms/maps/model/TileProvider.html", type:"class", deprecated:"false" }, + { id:155, label:"com.google.android.gms.maps.model.UrlTileProvider", link:"reference/com/google/android/gms/maps/model/UrlTileProvider.html", type:"class", deprecated:"false" }, + { id:156, label:"com.google.android.gms.maps.model.VisibleRegion", link:"reference/com/google/android/gms/maps/model/VisibleRegion.html", type:"class", deprecated:"false" }, + { id:157, label:"com.google.android.gms.panorama", link:"reference/com/google/android/gms/panorama/package-summary.html", type:"package", deprecated:"false" }, + { id:158, label:"com.google.android.gms.panorama.PanoramaClient", link:"reference/com/google/android/gms/panorama/PanoramaClient.html", type:"class", deprecated:"false" }, + { id:159, label:"com.google.android.gms.panorama.PanoramaClient.OnPanoramaInfoLoadedListener", link:"reference/com/google/android/gms/panorama/PanoramaClient.OnPanoramaInfoLoadedListener.html", type:"class", deprecated:"false" }, + { id:160, label:"com.google.android.gms.plus", link:"reference/com/google/android/gms/plus/package-summary.html", type:"package", deprecated:"false" }, + { id:161, label:"com.google.android.gms.plus.PlusClient", link:"reference/com/google/android/gms/plus/PlusClient.html", type:"class", deprecated:"false" }, + { id:162, label:"com.google.android.gms.plus.PlusClient.Builder", link:"reference/com/google/android/gms/plus/PlusClient.Builder.html", type:"class", deprecated:"false" }, + { id:163, label:"com.google.android.gms.plus.PlusClient.OnAccessRevokedListener", link:"reference/com/google/android/gms/plus/PlusClient.OnAccessRevokedListener.html", type:"class", deprecated:"false" }, + { id:164, label:"com.google.android.gms.plus.PlusClient.OnMomentsLoadedListener", link:"reference/com/google/android/gms/plus/PlusClient.OnMomentsLoadedListener.html", type:"class", deprecated:"false" }, + { id:165, label:"com.google.android.gms.plus.PlusClient.OnPeopleLoadedListener", link:"reference/com/google/android/gms/plus/PlusClient.OnPeopleLoadedListener.html", type:"class", deprecated:"false" }, + { id:166, label:"com.google.android.gms.plus.PlusClient.OrderBy", link:"reference/com/google/android/gms/plus/PlusClient.OrderBy.html", type:"class", deprecated:"false" }, + { id:167, label:"com.google.android.gms.plus.PlusOneButton", link:"reference/com/google/android/gms/plus/PlusOneButton.html", type:"class", deprecated:"false" }, + { id:168, label:"com.google.android.gms.plus.PlusOneButton.DefaultOnPlusOneClickListener", link:"reference/com/google/android/gms/plus/PlusOneButton.DefaultOnPlusOneClickListener.html", type:"class", deprecated:"false" }, + { id:169, label:"com.google.android.gms.plus.PlusOneButton.OnPlusOneClickListener", link:"reference/com/google/android/gms/plus/PlusOneButton.OnPlusOneClickListener.html", type:"class", deprecated:"false" }, + { id:170, label:"com.google.android.gms.plus.PlusOneButtonWithPopup", link:"reference/com/google/android/gms/plus/PlusOneButtonWithPopup.html", type:"class", deprecated:"false" }, + { id:171, label:"com.google.android.gms.plus.PlusOneDummyView", link:"reference/com/google/android/gms/plus/PlusOneDummyView.html", type:"class", deprecated:"false" }, + { id:172, label:"com.google.android.gms.plus.PlusShare", link:"reference/com/google/android/gms/plus/PlusShare.html", type:"class", deprecated:"false" }, + { id:173, label:"com.google.android.gms.plus.PlusShare.Builder", link:"reference/com/google/android/gms/plus/PlusShare.Builder.html", type:"class", deprecated:"false" }, + { id:174, label:"com.google.android.gms.plus.model.moments", link:"reference/com/google/android/gms/plus/model/moments/package-summary.html", type:"package", deprecated:"false" }, + { id:175, label:"com.google.android.gms.plus.model.moments.ItemScope", link:"reference/com/google/android/gms/plus/model/moments/ItemScope.html", type:"class", deprecated:"false" }, + { id:176, label:"com.google.android.gms.plus.model.moments.ItemScope.Builder", link:"reference/com/google/android/gms/plus/model/moments/ItemScope.Builder.html", type:"class", deprecated:"false" }, + { id:177, label:"com.google.android.gms.plus.model.moments.Moment", link:"reference/com/google/android/gms/plus/model/moments/Moment.html", type:"class", deprecated:"false" }, + { id:178, label:"com.google.android.gms.plus.model.moments.Moment.Builder", link:"reference/com/google/android/gms/plus/model/moments/Moment.Builder.html", type:"class", deprecated:"false" }, + { id:179, label:"com.google.android.gms.plus.model.moments.MomentBuffer", link:"reference/com/google/android/gms/plus/model/moments/MomentBuffer.html", type:"class", deprecated:"false" }, + { id:180, label:"com.google.android.gms.plus.model.people", link:"reference/com/google/android/gms/plus/model/people/package-summary.html", type:"package", deprecated:"false" }, + { id:181, label:"com.google.android.gms.plus.model.people.Person", link:"reference/com/google/android/gms/plus/model/people/Person.html", type:"class", deprecated:"false" }, + { id:182, label:"com.google.android.gms.plus.model.people.Person.AgeRange", link:"reference/com/google/android/gms/plus/model/people/Person.AgeRange.html", type:"class", deprecated:"false" }, + { id:183, label:"com.google.android.gms.plus.model.people.Person.Collection", link:"reference/com/google/android/gms/plus/model/people/Person.Collection.html", type:"class", deprecated:"true" }, + { id:184, label:"com.google.android.gms.plus.model.people.Person.Cover", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.html", type:"class", deprecated:"false" }, + { id:185, label:"com.google.android.gms.plus.model.people.Person.Cover.CoverInfo", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.CoverInfo.html", type:"class", deprecated:"false" }, + { id:186, label:"com.google.android.gms.plus.model.people.Person.Cover.CoverPhoto", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.CoverPhoto.html", type:"class", deprecated:"false" }, + { id:187, label:"com.google.android.gms.plus.model.people.Person.Cover.Layout", link:"reference/com/google/android/gms/plus/model/people/Person.Cover.Layout.html", type:"class", deprecated:"false" }, + { id:188, label:"com.google.android.gms.plus.model.people.Person.Emails", link:"reference/com/google/android/gms/plus/model/people/Person.Emails.html", type:"class", deprecated:"true" }, + { id:189, label:"com.google.android.gms.plus.model.people.Person.Emails.Type", link:"reference/com/google/android/gms/plus/model/people/Person.Emails.Type.html", type:"class", deprecated:"true" }, + { id:190, label:"com.google.android.gms.plus.model.people.Person.Gender", link:"reference/com/google/android/gms/plus/model/people/Person.Gender.html", type:"class", deprecated:"false" }, + { id:191, label:"com.google.android.gms.plus.model.people.Person.Image", link:"reference/com/google/android/gms/plus/model/people/Person.Image.html", type:"class", deprecated:"false" }, + { id:192, label:"com.google.android.gms.plus.model.people.Person.Name", link:"reference/com/google/android/gms/plus/model/people/Person.Name.html", type:"class", deprecated:"false" }, + { id:193, label:"com.google.android.gms.plus.model.people.Person.ObjectType", link:"reference/com/google/android/gms/plus/model/people/Person.ObjectType.html", type:"class", deprecated:"false" }, + { id:194, label:"com.google.android.gms.plus.model.people.Person.OrderBy", link:"reference/com/google/android/gms/plus/model/people/Person.OrderBy.html", type:"class", deprecated:"true" }, + { id:195, label:"com.google.android.gms.plus.model.people.Person.Organizations", link:"reference/com/google/android/gms/plus/model/people/Person.Organizations.html", type:"class", deprecated:"false" }, + { id:196, label:"com.google.android.gms.plus.model.people.Person.Organizations.Type", link:"reference/com/google/android/gms/plus/model/people/Person.Organizations.Type.html", type:"class", deprecated:"false" }, + { id:197, label:"com.google.android.gms.plus.model.people.Person.PlacesLived", link:"reference/com/google/android/gms/plus/model/people/Person.PlacesLived.html", type:"class", deprecated:"false" }, + { id:198, label:"com.google.android.gms.plus.model.people.Person.RelationshipStatus", link:"reference/com/google/android/gms/plus/model/people/Person.RelationshipStatus.html", type:"class", deprecated:"false" }, + { id:199, label:"com.google.android.gms.plus.model.people.Person.Urls", link:"reference/com/google/android/gms/plus/model/people/Person.Urls.html", type:"class", deprecated:"false" }, + { id:200, label:"com.google.android.gms.plus.model.people.Person.Urls.Type", link:"reference/com/google/android/gms/plus/model/people/Person.Urls.Type.html", type:"class", deprecated:"false" }, + { id:201, label:"com.google.android.gms.plus.model.people.PersonBuffer", link:"reference/com/google/android/gms/plus/model/people/PersonBuffer.html", type:"class", deprecated:"false" } ]; diff --git a/docs/html/samples/images/ActionBarCompat1.png b/docs/html/samples/images/ActionBarCompat1.png new file mode 100644 index 0000000000000000000000000000000000000000..64d3e66f621b3cab3184ef5c2a52d6468ffcd243 Binary files /dev/null and b/docs/html/samples/images/ActionBarCompat1.png differ diff --git a/docs/html/samples/images/ActionBarCompat2.png b/docs/html/samples/images/ActionBarCompat2.png new file mode 100644 index 0000000000000000000000000000000000000000..04a7e6c5fecae00bb139153bd7897b9bfe030525 Binary files /dev/null and b/docs/html/samples/images/ActionBarCompat2.png differ diff --git a/docs/html/samples/index.jd b/docs/html/samples/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..3ea5245d7750803ccccf2eefc9147bf0155e9e1b --- /dev/null +++ b/docs/html/samples/index.jd @@ -0,0 +1,11 @@ +page.title=Samples +@jd:body + + +
        +

        Some kind of sample sorting will appear here.

        +
        + + + diff --git a/docs/html/samples/samples_toc.cs b/docs/html/samples/samples_toc.cs new file mode 100644 index 0000000000000000000000000000000000000000..14a5b0a9d87b7ea4338e12e166e07d996981b0c8 --- /dev/null +++ b/docs/html/samples/samples_toc.cs @@ -0,0 +1,16 @@ + + + diff --git a/docs/html/samples/topic.jd b/docs/html/samples/topic.jd new file mode 100644 index 0000000000000000000000000000000000000000..cac9b10704f6bfdabfe64a2785c1912b1e402ecb --- /dev/null +++ b/docs/html/samples/topic.jd @@ -0,0 +1,26 @@ +page.title=Samples +@jd:body + + +
        +
        + + + + diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd index 4ea375263d7d90a7cda77dcca3455f7c419e0855..eb2d6a702f850d742261587894cd5f762293a8a2 100644 --- a/docs/html/sdk/index.jd +++ b/docs/html/sdk/index.jd @@ -5,43 +5,43 @@ header.hide=1 page.metaDescription=Download the official Android SDK to develop apps for Android-powered devices. -sdk.linux32_bundle_download=adt-bundle-linux-x86-20130729.zip -sdk.linux32_bundle_bytes=457716139 -sdk.linux32_bundle_checksum=b3686d10dc1cbceba1074404d4386283 +sdk.linux32_bundle_download=adt-bundle-linux-x86-20130917.zip +sdk.linux32_bundle_bytes=474924071 +sdk.linux32_bundle_checksum=912b2dac6e0a4fa4ae1417271bf42863 -sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20130729.zip -sdk.linux64_bundle_bytes=458006784 -sdk.linux64_bundle_checksum=1fabcc3f772ba8b2fc194d6e0449da17 +sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20130917.zip +sdk.linux64_bundle_bytes=475215747 +sdk.linux64_bundle_checksum=2f7523d4eba9a8302c3c4a3955785e18 -sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20130729.zip -sdk.mac64_bundle_bytes=428792424 -sdk.mac64_bundle_checksum=6c42b9966abcfa8a75c0ee83d0d95882 +sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20130917.zip +sdk.mac64_bundle_bytes=448581372 +sdk.mac64_bundle_checksum=4e2d599486ecc935d24eeef5eb641364 -sdk.win32_bundle_download=adt-bundle-windows-x86-20130729.zip -sdk.win32_bundle_bytes=463931746 -sdk.win32_bundle_checksum=51faf4e5fdf9c5b4a176179a99ce3511 +sdk.win32_bundle_download=adt-bundle-windows-x86-20130917.zip +sdk.win32_bundle_bytes=481803289 +sdk.win32_bundle_checksum=5d6c79a47c8b47170cff3d231dcf7ad3 -sdk.win64_bundle_download=adt-bundle-windows-x86_64-20130729.zip -sdk.win64_bundle_bytes=464064756 -sdk.win64_bundle_checksum=e8f05c1fddb8e609e880de23113c7426 +sdk.win64_bundle_download=adt-bundle-windows-x86_64-20130917.zip +sdk.win64_bundle_bytes=481934982 +sdk.win64_bundle_checksum=918f80aad61ec21509d86a2fbd87fd44 -sdk.linux_download=android-sdk_r22.0.5-linux.tgz -sdk.linux_bytes=105641005 -sdk.linux_checksum=8201b10c21510f082c54f58a9bb082c8 +sdk.linux_download=android-sdk_r22.2.1-linux.tgz +sdk.linux_bytes=100918342 +sdk.linux_checksum=05911d3052a1cbf678561104d35a1bc0 -sdk.mac_download=android-sdk_r22.0.5-macosx.zip -sdk.mac_bytes=77225724 -sdk.mac_checksum=94f3cbe896c332b94ee0408ae610a4b8 +sdk.mac_download=android-sdk_r22.2.1-macosx.zip +sdk.mac_bytes=74859877 +sdk.mac_checksum=727a51affa2af733eca1aa307c73c3bd -sdk.win_download=android-sdk_r22.0.5-windows.zip -sdk.win_bytes=113510621 -sdk.win_checksum=30695dffc41e0d7cf9ff948ab0c48920 +sdk.win_download=android-sdk_r22.2.1-windows.zip +sdk.win_bytes=108797377 +sdk.win_checksum=bea5d28cfb6c073b32643dd3ed0bc1e0 -sdk.win_installer=installer_r22.0.5-windows.exe -sdk.win_installer_bytes=93505782 -sdk.win_installer_checksum=940849be19ac6151e3e35c8706c81d86 +sdk.win_installer=installer_r22.2.1-windows.exe +sdk.win_installer_bytes=88795776 +sdk.win_installer_checksum=07e6e47de6c4549bea6986453119b37c diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd index bdc07d02283d15c843957a1902cecd2b497f9862..66b1c435cd2b7f213a794bb8381762e339ae5745 100644 --- a/docs/html/sdk/installing/installing-adt.jd +++ b/docs/html/sdk/installing/installing-adt.jd @@ -1,8 +1,8 @@ page.title=Installing the Eclipse Plugin -adt.zip.version=22.0.5 -adt.zip.download=ADT-22.0.5.zip -adt.zip.bytes=16839757 -adt.zip.checksum=1097fccf32063e3638a9d27aa0f295ca +adt.zip.version=22.2.1 +adt.zip.download=ADT-22.2.1.zip +adt.zip.bytes=14476845 +adt.zip.checksum=97176754a1e86adf2e5e05f44dc7229e @jd:body diff --git a/docs/html/sdk/installing/studio.jd b/docs/html/sdk/installing/studio.jd index 5a47f85d62ca777ac2e328ad6099f16f9e8191b8..3399ee817218621cff6502f3d0971646e4505a43 100644 --- a/docs/html/sdk/installing/studio.jd +++ b/docs/html/sdk/installing/studio.jd @@ -187,7 +187,7 @@ This is the Android Software Development Kit License Agreement
        -
        +
      • Template-based wizards to create common Android designs and components.
      • A rich layout editor that allows you to drag-and-drop UI components, preview layouts on multiple screen configurations, and much more.
      • +
      • Built-in support for Google Cloud Platform, making it easy to integrate Google Cloud + Messaging and App Engine as server-side components.

        Caution: Android Studio is currently available as diff --git a/docs/html/sitemap.txt b/docs/html/sitemap.txt index 3a416f99ac611521675f32d589eb1e310af5b15f..9bff5d45a5cec1d432578fc37848eb03513bf0f1 100644 --- a/docs/html/sitemap.txt +++ b/docs/html/sitemap.txt @@ -74,7 +74,7 @@ http://developer.android.com/downloads/design/Android_Design_Fireworks_Stencil_2 http://developer.android.com/downloads/design/Android_Design_Illustrator_Vectors_20120814.ai http://developer.android.com/downloads/design/Android_Design_OmniGraffle_Stencil_20120814.graffle http://developer.android.com/downloads/design/Android_Design_Holo_Widgets_20120814.zip -http://developer.android.com/downloads/design/Android_Design_Icons_20120814.zip +http://developer.android.com/downloads/design/Android_Design_Icons_20130926.zip http://developer.android.com/downloads/design/Roboto_Hinted_20120823.zip http://developer.android.com/downloads/design/Roboto_Specimen_Book_20111129.pdf http://developer.android.com/downloads/design/Android_Design_Color_Swatches_20120229.zip diff --git a/docs/html/tools/debugging/debugging-memory.jd b/docs/html/tools/debugging/debugging-memory.jd new file mode 100644 index 0000000000000000000000000000000000000000..0454293e2d44022af5aaa7f259df7d4d63a43951 --- /dev/null +++ b/docs/html/tools/debugging/debugging-memory.jd @@ -0,0 +1,494 @@ +page.title=Investigating Your RAM Usage +page.tags="memory","OutOfMemoryError" +@jd:body + +

        + + + + +

        Because Android is designed for mobile devices, you should always be careful about how much +random-access memory (RAM) your app uses. Although Android’s Dalvik virtual machine performs +routine garbage collection, this doesn’t mean you can ignore when and where your app allocates and +releases memory. In order to provide a stable user experience that allows the system to quickly +switch between apps, it’s important that your app does not needlessly consume memory when the user +is not interacting with it.

        + +

        Even if you follow all the best practices for Managing Your App Memory during +development (which you should), you still might leak objects or introduce other memory bugs. The +only way to be certain your app is using as little memory as possible is to analyze your app’s +memory usage with tools. This guide shows you how to do that.

        + + +

        Interpreting Log Messages

        + +

        The simplest place to begin investigating your apps memory usage is the Dalvik log messages. You'll +find these log messages in logcat (the output is +available in the Device Monitor or directly in IDEs such as Eclipse and Android Studio).

        + +

        Every time a garbage collection occurs, logcat prints a message with the following information:

        + +
        +D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
        +
        + +
        +
        GC Reason
        +
        +What triggered the garbage collection and what kind of collection it is. Reasons that may appear +include: +
        +
        GC_CONCURRENT
        +
        A concurrent garbage collection that frees up memory as your heap begins to fill up.
        + +
        GC_FOR_MALLOC
        +
        A garbage collection caused because your app attempted to allocate memory when your heap was +already full, so the system had to stop your app and reclaim memory.
        + +
        GC_HPROF_DUMP_HEAP
        +
        A garbage collection that occurs when you create an HPROF file to analyze your heap.
        + +
        GC_EXPLICIT +
        An explicit garbage collection, such as when you call {@link java.lang.System#gc()} (which you +should avoid calling and instead trust the garbage collector to run when needed).
        + +
        GC_EXTERNAL_ALLOC
        +
        This happens only on API level 10 and lower (newer versions allocate everything in the Dalvik +heap). A garbage collection for externally allocated memory (such as the pixel data stored in +native memory or NIO byte buffers).
        +
        +
        + +
        Amount freed
        +
        The amount of memory reclaimed from this garbage collection.
        + +
        Heap stats
        +
        Percentage free and (number of live objects)/(total heap size).
        + +
        External memory stats
        +
        Externally allocated memory on API level 10 and lower (amount of allocated memory) / (limit at +which collection will occur).
        + +
        Pause time
        +
        Larger heaps will have larger pause times. Concurrent pause times show two pauses: one at the +beginning of the collection and another near the end.
        +
        + +

        For example:

        + +
        +D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
        +
        + +

        As these log messages stack up, look out for increases in the heap stats (the +{@code 3571K/9991K} value in the above example). If this value +continues to increase and doesn't ever seem to get smaller, you could have a memory leak.

        + + +

        Viewing Heap Updates

        + +

        To get a little information about what kind of memory your app is using and when, you can view +real-time updates to your app's heap in the Device Monitor:

        + +
          +
        1. Open the Device Monitor. +

          From your <sdk>/tools/ directory, launch the monitor tool.

          +
        2. +
        3. In the Debug Monitor window, select your app's process from the list on the left.
        4. +
        5. Click Update Heap above the process list.
        6. +
        7. In the right-side panel, select the Heap tab.
        8. +
        + +

        The Heap view shows some basic stats about your heap memory usage, updated after every +garbage collection. To see the first update, click the Cause GC button.

        + + +

        Figure 1. The Device Monitor tool, +showing the [1] Update Heap and [2] Cause GC buttons. +The Heap tab on the right shows the heap results.

        + +

        Continue interacting with your app to watch your heap allocation update with each garbage +collection. This can help you identify which actions in your app are likely causing too much +allocation and where you should try to reduce allocations and release +resources.

        + + + +

        Tracking Allocations

        + +

        As you start narrowing down memory issues, you should also use the Allocation Tracker to +get a better understanding of where your memory-hogging objects are allocated. The Allocation +Tracker can be useful not only for looking at specific uses of memory, but also to analyze critical +code paths in an app such as scrolling.

        + +

        For example, tracking allocations when flinging a list in your app allows you to see all the +allocations that need to be done for that behavior, what thread they are on, and where they came +from. This is extremely valuable for tightening up these paths to reduce the work they need and +improve the overall smoothness of the UI.

        + +

        To use Allocation Tracker:

        +
          +
        1. Open the Device Monitor. +

          From your <sdk>/tools/ directory, launch the monitor tool.

          +
        2. +
        3. In the DDMS window, select your app's process in the left-side panel.
        4. +
        5. In the right-side panel, select the Allocation Tracker tab.
        6. +
        7. Click Start Tracking.
        8. +
        9. Interact with your app to execute the code paths you want to analyze.
        10. +
        11. Click Get Allocations every time you want to update the +list of allocations.
        12. +
        + +

        The list shows all recent allocations, +currently limited by a 512-entry ring buffer. Click on a line to see the stack trace that led to +the allocation. The trace shows you not only what type of object was allocated, but also in which +thread, in which class, in which file and at which line.

        + + +

        Figure 2. The Device Monitor tool, +showing recent app allocations and stack traces in the Allocation Tracker.

        + + +

        Note: You will always see some allocations from {@code +DdmVmInternal} and else where that come from the allocation tracker itself.

        + +

        Although it's not necessary (nor possible) to remove all allocations for your performance +critical code paths, the allocation tracker can help you identify important issues in your code. +For instance, some apps might create a new {@link android.graphics.Paint} object on every draw. +Moving that object into a global member is a simple fix that helps improve performance.

        + + + + + + +

        Viewing Overall Memory Allocations

        + +

        For further analysis, you may want to observe how that your app's memory is +divided between different categories, which you can do with the adb meminfo data.

        + +

        When talking about how much RAM your app is using with this data, the key metrics +discussed below are:

        + +
        +
        Private (Clean and Dirty) RAM
        +
        This is memory that is being used by only your process. This is the bulk of the RAM that the system +can reclaim when your app’s process is destroyed. Generally, the most important portion of this is +“private dirty†RAM, which is the most expensive because it is used by only your process and its +contents exist only in RAM so can’t be paged to storage (because Android does not use swap). All +Dalvik and native heap allocations you make will be private dirty RAM; Dalvik and native +allocations you share with the Zygote process are shared dirty RAM.
        + +
        Proportional Set Size (PSS)
        +
        This is a measurement of your app’s RAM use that takes into account sharing pages across processes. +Any RAM pages that are unique to your process directly contribute to its PSS value, while pages +that are shared with other processes contribute to the PSS value only in proportion to the amount +of sharing. For example, a page that is shared between two processes will contribute half of its +size to the PSS of each process.
        +
        + + +

        A nice characteristic of the PSS measurement is that you can add up the PSS across all processes to +determine the actual memory being used by all processes. This means PSS is a good measure for the +actual RAM weight of a process and for comparison against the RAM use of other processes and the +total available RAM.

        + +

        You can look at the memory use of your app (measured in kilobytes) with the +following adb command:

        + +
        +adb shell dumpsys meminfo <package_name>
        +
        + +

        For example, below is the the output for Gmail’s process on a tablet device. There is a lot of +information here, but key points for discussion are highlighted in different colors.

        + +

        Note: The information you see may vary slightly from what is shown +here, as some details of the output differ across platform versions.

        + +
        +** MEMINFO in pid 9953 [com.google.android.gm] **
        +                 Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
        +               Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
        +              ------  ------  ------  ------  ------  ------  ------  ------  ------
        +  Native Heap      0       0       0       0       0       0    7800    7637(6)  126
        +  Dalvik Heap   5110(3)    0    4136    4988(3)    0       0    9168    8958(6)  210
        + Dalvik Other   2850       0    2684    2772       0       0
        +        Stack     36       0       8      36       0       0
        +       Cursor    136       0       0     136       0       0
        +       Ashmem     12       0      28       0       0       0
        +    Other dev    380       0      24     376       0       4
        +     .so mmap   5443(5) 1996    2584    2664(5) 5788    1996(5)
        +    .apk mmap    235      32       0       0    1252      32
        +    .ttf mmap     36      12       0       0      88      12
        +    .dex mmap   3019(5) 2148       0       0    8936    2148(5)
        +   Other mmap    107       0       8       8     324      68
        +      Unknown   6994(4)    0     252    6992(4)    0       0
        +        TOTAL  24358(1) 4188    9724   17972(2)16388    4260(2)16968   16595     336
        + 
        + Objects
        +               Views:    426         ViewRootImpl:        3(8)
        +         AppContexts:      6(7)        Activities:        2(7)
        +              Assets:      2        AssetManagers:        2
        +       Local Binders:     64        Proxy Binders:       34
        +    Death Recipients:      0
        +     OpenSSL Sockets:      1
        + 
        + SQL
        +         MEMORY_USED:   1739
        +  PAGECACHE_OVERFLOW:   1164          MALLOC_SIZE:       62
        +
        + +

        Generally, you should be concerned with only the Pss Total and Private Dirty +columns. In some cases, the Private Clean and Heap Alloc columns also offer +interesting data. Here is some more information about the different memory allocations (the rows) +you should observe: + +

        +
        Dalvik Heap
        +
        The RAM used by Dalvik allocations in your app. The Pss Total includes all Zygote +allocations (weighted by their sharing across processes, as described in the PSS definition above). +The Private Dirty number is the actual RAM committed to only your app’s heap, composed of +your own allocations and any Zygote allocation pages that have been modified since forking your +app’s process from Zygote. + +

        Note: On newer platform versions that have the Dalvik +Other section, the Pss Total and Private Dirty numbers for Dalvik Heap do +not include Dalvik overhead such as the just-in-time compilation (JIT) and garbage collection (GC) +bookkeeping, whereas older versions list it all combined under Dalvik.

        + +

        The Heap Alloc is the amount of memory that the Dalvik and native heap allocators keep +track of for your app. This value is larger than Pss Total and Private Dirty +because your process was forked from Zygote and it includes allocations that your process shares +with all the others.

        +
        + +
        .so mmap and .dex mmap
        +
        The RAM being used for mmapped .so (native) and .dex (Dalvik) code. The +Pss Total number includes platform code shared across apps; the Private Clean is +your app’s own code. Generally, the actual mapped size will be much larger—the RAM here is only +what currently needs to be in RAM for code that has been executed by the app. However, the .so mmap +has a large private dirty, which is due to fix-ups to the native code when it was loaded into its +final address. +
        + +
        Unknown
        +
        Any RAM pages that the system could not classify into one of the other more specific items. +Currently, this contains mostly native allocations, which cannot be identified by the tool when +collecting this data due to Address Space Layout Randomization (ASLR). As with the Dalvik heap, the +Pss Total for Unknown takes into account sharing with Zygote, and Private Dirty +is unknown RAM dedicated to only your app. +
        + +
        TOTAL
        +
        The total Proportional Set Size (PSS) RAM used by your process. This is the sum of all PSS fields +above it. It indicates the overall memory weight of your process, which can be directly compared +with other processes and the total available RAM. + +

        The Private Dirty and Private Clean are the total allocations within your +process, which are not shared with other processes. Together (especially Private Dirty), +this is the amount of RAM that will be released back to the system when your process is destroyed. +Dirty RAM is pages that have been modified and so must stay committed to RAM (because there is no +swap); clean RAM is pages that have been mapped from a persistent file (such as code being +executed) and so can be paged out if not used for a while.

        + +
        + +
        ViewRootImpl
        +
        The number of root views that are active in your process. Each root view is associated with a +window, so this can help you identify memory leaks involving dialogs or other windows. +
        + +
        AppContexts and Activities
        +
        The number of app {@link android.content.Context} and {@link android.app.Activity} objects that +currently live in your process. This can be useful to quickly identify leaked {@link +android.app.Activity} objects that can’t be garbage collected due to static references on them, +which is common. These objects often have a lot of other allocations associated with them and so +are a good way to track large memory leaks.
        + +

        Note: A {@link android.view.View} or {@link +android.graphics.drawable.Drawable} object also holds a reference to the {@link +android.app.Activity} that it's from, so holding a {@link android.view.View} or {@link +android.graphics.drawable.Drawable} object can also lead to your app leaking an {@link +android.app.Activity}.

        + + +
        + + + + + + + + + +

        Capturing a Heap Dump

        + +

        A heap dump is a snapshot of all the objects in your app's heap, stored in a binary format called +HPROF. Your app's heap dump provides information about the overall state of your app's heap so you +can track down problems you might have identified while viewing heap updates.

        + +

        To retrieve your heap dump:

        +
          +
        1. Open the Device Monitor. +

          From your <sdk>/tools/ directory, launch the monitor tool.

          +
        2. +
        3. In the DDMS window, select your app's process in the left-side panel.
        4. +
        5. Click Dump HPROF file, shown in figure 3.
        6. +
        7. In the window that appears, name your HPROF file, select the save location, +then click Save.
        8. +
        + + +

        Figure 3. The Device Monitor tool, +showing the [1] Dump HPROF file button.

        + +

        If you need to be more precise about when the dump is created, you can also create a heap dump +at the critical point in your app code by calling {@link android.os.Debug#dumpHprofData +dumpHprofData()}.

        + +

        The heap dump is provided in a format that's similar to, but not identical to one from the Java +HPROF tool. The major difference in an Android heap dump is due to the fact that there are a large +number of allocations in the Zygote process. But because the Zygote allocations are shared across +all app processes, they don’t matter very much to your own heap analysis.

        + +

        To analyze your heap dump, you can use a standard tool like jhat or the Eclipse Memory Analyzer Tool (MAT). However, first +you'll need to convert the HPROF file from Android's format to the J2SE HPROF format. You can do +this using the hprof-conv tool provided in the <sdk>/tools/ +directory. Simply run the hprof-conv command with two arguments: the original HPROF +file and the location to write the converted HPROF file. For example:

        + +
        +hprof-conv heap-original.hprof heap-converted.hprof
        +
        + +

        Note: If you're using the version of DDMS that's integrated into +Eclipse, you do not need to perform the HPROF converstion—it performs the conversion by +default.

        + +

        You can now load the converted file in MAT or another heap analysis tool that understands +the J2SE HPROF format.

        + +

        When analyzing your heap, you should look for memory leaks caused by:

        +
          +
        • Long-lived references to an Activity, Context, View, Drawable, and other objects that may hold a +reference to the container Activity or Context.
        • +
        • Non-static inner classes (such as a Runnable, which can hold the Activity instance).
        • +
        • Caches that hold objects longer than necessary.
        • +
        + + +

        Using the Eclipse Memory Analyzer Tool

        + +

        The Eclipse Memory Analyzer Tool (MAT) is just one +tool that you can use to analyze your heap dump. It's also quite powerful so most of its +capabilities are beyond the scope of this document, but here are a few tips to get you started. + +

        Once you open your converted HPROF file in MAT, you'll see a pie chart in the Overview, +showing what your largest objects are. Below this chart, are links to couple of useful features:

        + +
          +
        • The Histogram view shows a list of all classes and how many instances + there are of each. +

          You might want to use this view to find extra instances of classes for which you know there + should be only a certain number. For example, a common source of leaks is additional instance of + your {@link android.app.Activity} class, for which you should usually have only one instance + at a time. To find a specific class instance, type the class name into the <Regex> + field at the top of the list. +

          When you find a class with too many instances, right-click it and select + List objects > with incoming references. In the list that + appears, you can determine where an instance is retained by right-clicking it and selecting + Path To GC Roots > exclude weak references.

          +
        • + +
        • The Dominator tree shows a list of objects organized by the amount + of retained heap. +

          What you should look for is anything that's retaining a portion of heap that's roughly + equivalent to the memory size you observed leaking from the GC logs, + heap updates, or allocation + tracker. +

          When you see something suspicious, right-click on the item and select + Path To GC Roots > exclude weak references. This opens a + new tab that traces the references to that object which is causing the alleged leak.

          + +

          Note: Most apps will show an instance of + {@link android.content.res.Resources} near the top with a good chunk of heap, but this is + usually expected when your app uses lots of resources from your {@code res/} directory.

          +
        • +
        + + + +

        Figure 4. The Eclipse Memory Analyzer Tool (MAT), +showing the Histogram view and a search for "MainActivity".

        + +

        For more information about MAT, watch the Google I/O 2011 presentation, +Memory management for Android apps, +which includes a walkthrough using MAT beginning at about 21:10. +Also refer to the Eclipse Memory +Analyzer documentation.

        + +

        Comparing heap dumps

        + +

        You may find it useful to compare your app's heap state at two different points in time in order +to inspect the changes in memory allocation. To compare two heap dumps using MAT:

        + +
          +
        1. Create two HPROF files as described above, in Capturing a Heap Dump. +
        2. Open the first HPROF file in MAT (File > Open Heap Dump). +
        3. In the Navigation History view (if not visible, select Window > + Navigation History), right-click on Histogram and select + Add to Compare Basket. +
        4. Open the second HPROF file and repeat steps 2 and 3. +
        5. Switch to the Compare Basket view and click Compare the Results + (the red "!" icon in the top-right corner of the view). +
        + + + + + + +

        Triggering Memory Leaks

        + +

        While using the tools described above, you should aggressively stress your app code and try +forcing memory leaks. One way to provoke memory leaks in your app is to let it +run for a while before inspecting the heap. Leaks will trickle up to the top of the allocations in +the heap. However, the smaller the leak, the longer you need to run the app in order to see it.

        + +

        You can also trigger a memory leak in one of the following ways:

        +
          +
        1. Rotate the device from portrait to landscape and back again multiple times while in different +activity states. Rotating the device can often cause an app to leak an {@link android.app.Activity}, +{@link android.content.Context}, or {@link android.view.View} object because the system +recreates the {@link android.app.Activity} and if your app holds a reference +to one of those objects somewhere else, the system can't garbage collect it.
        2. +
        3. Switch between your app and another app while in different activity states (navigate to +the Home screen, then return to your app).
        4. +
        + +

        Tip: You can also perform the above steps by using the "monkey" +test framework. For more information on running the monkey test framework, read the monkeyrunner +documentation.

        \ No newline at end of file diff --git a/docs/html/tools/help/monitor.jd b/docs/html/tools/help/monitor.jd index 18fb49a3b3f0e947bcd056422b62c5c3f88fcb8b..e1fe7721a1360c3ac67d30443c394e3a969e8944 100644 --- a/docs/html/tools/help/monitor.jd +++ b/docs/html/tools/help/monitor.jd @@ -1,6 +1,18 @@ page.title=Device Monitor @jd:body +
        +
        +

        See also

        + +
          +
        1. Investigating Your RAM Usage
        2. +
        +
        +
        + +

        Android Device Monitor is a stand-alone tool that provides a graphical user interface for several Android application debugging and analysis tools. The Monitor tool does not require installation of a integrated development environment, such as Eclipse, and encapsulates the @@ -14,6 +26,7 @@ following tools:

      • Pixel Perfect magnification viewer
      • +

        Usage

        To start Device Monitor, enter the following command from the SDK tools/ @@ -22,3 +35,7 @@ directory:

        Start an Android emulator or connect an Android device via USB cable, and connect Device Monitor to the device by selecting it in the Devices window.

        + +

        Note: Only one debugger can be connected to your device at a time. +If you're using ADT, you may need to close the debugging tool before launching the Device Monitor +in order for the device to be fully debuggable.

        diff --git a/docs/html/tools/revisions/platforms.jd b/docs/html/tools/revisions/platforms.jd index 820edbd8f44ac7da74921c446700b27ad39d1d9a..02216de9a071ee00ea9d610befc6410555a3795a 100644 --- a/docs/html/tools/revisions/platforms.jd +++ b/docs/html/tools/revisions/platforms.jd @@ -22,20 +22,31 @@ Highlights and APIs -

        To develop an Android app, you must install at least one Android platform from the SDK Manager -against which you can compile your app. Often, any given version of the Android will be revised -with bug fixes or other changes, as denoted by the revision number. Below, you'll find the -release notes for each version of the platform and the subsequent revisions to the platform -version.

        - -

        To determine what revision of an Android platform you have installed, refer to the -Installed Packages listing in the Android +

        This document provides information about Android platform releases. In order to compile your +application against a particular platform release, you must download and install the SDK Platform +for that release. If you want to test your application on an emulator, you must also download at +least one system image for that platform release.

        + +

        Each platform release includes system images that support a specific processor architecture, +such as ARM EABI, Intel x86 or MIPS. Platform releases also include a system image that contains +Google APIs. The SDK Manager lists available +platform system images under each platform version header, for example:

        + +
          +
        • ARM EABI v7a System Image
        • +
        • Intel x86 Atom System Image
        • +
        • MIPS System Image
        • +
        • Google APIs
        • +
        + +

        To determine what revisions of an Android platform you have installed, refer to the +Packages listing in the Android SDK Manager.

        Important: To download the most recent Android -system components from the Android SDK Manager, you must first update the SDK Tools to -revision 22 or later and restart the SDK Manager. If you do not, -the latest Android system components will not be available for download.

        +system components from the Android SDK Manager, you must first update the SDK Tools to the +most recent release and restart the SDK Manager. If you do not, the latest Android system +components will not be available for download.

        @@ -46,6 +57,25 @@ the latest Android system components will not be available for download.

        Revision 2 (August 2013) +

        + +
        + +

        Maintenance update. The system version is 4.3.

        +
        +
        Dependencies:
        +
        Android SDK Platform-tools r18 or higher is required.
        +
        Android SDK Tools 22.0.4 or higher is recommended.
        +
        + +
        +
        + +
        + +

        + Revision 1 (July 2013)

        @@ -61,6 +91,54 @@ class="toggle-content-img" alt="" />Revision 1 (July 2013)
        +

        Google APIs System Image

        + +
        +

        + Revision 3 (September 2013) +

        + +
        + +

        Maintenance update. This release includes + Google Play services version 3.2.65, + allowing you to test your application in an emulator using the latest Google Play services.

        + +
        +
        + +
        +

        + Revision 2 (August 2013) +

        + +
        + +

        Maintenance update. This release includes + Google Play services version 3.2.25, + allowing you to test your application in an emulator using the latest Google Play services.

        + +
        +
        + +
        +

        + Revision 1 (July 2013) +

        + +
        + +

        Initial release. This release includes + Google Play services version 3.1.58.

        + +
        +
        + + +

        Android 4.2

        diff --git a/docs/html/tools/sdk/eclipse-adt.jd b/docs/html/tools/sdk/eclipse-adt.jd index e9c514e064d33058f56cdb7291bd0d6bfbfe9ff2..cfdf8ccf507ca3e371fca49f6b1e1d2e0db17a2e 100644 --- a/docs/html/tools/sdk/eclipse-adt.jd +++ b/docs/html/tools/sdk/eclipse-adt.jd @@ -57,6 +57,78 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues

        ADT 22.2.1 (September 2013) +

        + +
        +
        +
        Dependencies:
        + +
        +
          +
        • Java 1.6 or higher is required.
        • +
        • Eclipse Helios (Version 3.6.2) or higher is required.
        • +
        • This version of ADT is designed for use with + SDK Tools r22.2.1. + If you haven't already installed SDK Tools r22.2.1 into your SDK, use the + Android SDK Manager to do so.
        • +
        +
        + +
        General Notes:
        +
        +
          +
        • Fixed problem with templates that causes the new project wizard to hang. + (Issue 60149)
        • +
        +
        + +
        +
        +
        + + +
        +

        + ADT 22.2 (September 2013) +

        + +
        +
        +
        Dependencies:
        + +
        +
          +
        • Java 1.6 or higher is required.
        • +
        • Eclipse Helios (Version 3.6.2) or higher is required.
        • +
        • This version of ADT is designed for use with + SDK Tools r22.2. + If you haven't already installed SDK Tools r22.2 into your SDK, use the + Android SDK Manager to do so.
        • +
        +
        + +
        General Notes:
        +
        +
          +
        • Updated build tools to allow use of RenderScript on older versions of Android + using new features in the + Support Library.
        • +
        • Reverted signing changes that sometimes trigger a signing verification problem on older + platforms.
        • +
        • Fixed problem with gradle export function for the Windows platform.
        • +
        +
        + +
        +
        +
        + + +
        +

        + ADT 22.0.5 (July 2013)

        @@ -78,7 +150,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
        General Notes:
          -
        • Fixed Renderscript compilation issue for Windows platforms.
        • +
        • Fixed RenderScript compilation issue for Windows platforms.
        • Updated Systrace report generation in the Monitor and DDMS perspectives.
        @@ -113,7 +185,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
        General Notes:
          -
        • Fixed problem with compiling Renderscript code.
        • +
        • Fixed problem with compiling RenderScript code.
        • Improved Gradle export with better workflow and error reporting.
        • Improved Gradle multi-module export feature.
        • Updated build logic to force exporting of the classpath containers unless you are using @@ -1005,7 +1077,7 @@ href="http://tools.android.com/recent/lint">more info)
        • Bug fixes:
            -
          • Fixed build issue when using Renderscript in projects that target API levels 11-13 +
          • Fixed build issue when using RenderScript in projects that target API levels 11-13 (Issue 21006).
          • Fixed issue when creating projects from existing source code.
          • Fixed issues in the SDK Manager diff --git a/docs/html/tools/sdk/tools-notes.jd b/docs/html/tools/sdk/tools-notes.jd index 4aef8a02850584dbde329b718103a4d812fc588b..25c409e32fa9e705683afcd90254da09e924c1c1 100644 --- a/docs/html/tools/sdk/tools-notes.jd +++ b/docs/html/tools/sdk/tools-notes.jd @@ -30,6 +30,88 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues

            SDK Tools, Revision 22.2.1 (September 2013) +

            + +
            + +
            +
            Dependencies:
            +
            +
              +
            • Android SDK Platform-tools revision 16 or later.
            • +
            • If you are developing in Eclipse with ADT, note that this version of SDK Tools is + designed for use with ADT 22.2.1 and later. If you haven't already, update your + ADT Plugin to 22.2.1.
            • +
            • If you are developing outside Eclipse, you must have + Apache Ant 1.8 or later.
            • +
            +
            + +
            General Notes:
            +
            +
              +
            • Fixed problem with templates that causes the new project wizard to hang. + (Issue 60149)
            • +
            • Fixed crash when using the lint command line tool because of mis-matched library + dependency. (Issue 60190)
            • +
            +
            +
            +
            +
            + +
            +

            + SDK Tools, Revision 22.2 (September 2013) +

            + +
            + +
            +
            Dependencies:
            +
            +
              +
            • Android SDK Platform-tools revision 16 or later.
            • +
            • If you are developing in Eclipse with ADT, note that this version of SDK Tools is + designed for use with ADT 22.2 and later. If you haven't already, update your + ADT Plugin to 22.2.
            • +
            • If you are developing outside Eclipse, you must have + Apache Ant 1.8 or later.
            • +
            +
            + +
            General Notes:
            +
            +
              +
            • Updated build tools to allow use of RenderScript on older versions of Android + using new features in the + Support Library.
            • +
            • Moved the Systrace tool to the {@code >sdk</platform-tools/} directory.
            • +
            • Modified Tracer for OpenGL ES to + support OpenGL ES 3.0.
            • +
            • Lint +
                +
              • Fixed problem with lint not detecting custom namespaces. + (Issue 55673)
              • +
              • Fixed problem with the XML report including invalid characters. + (Issue 56205)
              • +
              • Fixed command-line execution of lint to work in headless mode to support execution + by build servers. (Issue 55820)
              • +
              +
            • +
            • Improved support for path names with spaces in the Windows command-line tools.
            • +
            +
            +
            +
            +
            + + +
            +

            + SDK Tools, Revision 22.0.5 (July 2013)

            @@ -55,10 +137,10 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
            General Notes:
              -
            • Fixed Renderscript compilation issue for Windows platforms with ant.
            • +
            • Fixed RenderScript compilation issue for Windows platforms with ant.
            • Updated Systrace to work with the Android 4.3 platform image.
            • -
            • Fixed packaging of Renderscript compiler.
            • +
            • Fixed packaging of RenderScript compiler.
            • Build tools 18.0.0 is obsolete and 18.0.1 should be used instead.
            @@ -95,7 +177,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
            General Notes:
              -
            • Fixed problem with compiling Renderscript code.
            • +
            • Fixed problem with compiling RenderScript code.
            @@ -274,17 +356,17 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
          -
        • Renderscript +
        • RenderScript
          • Added support for Filterscript compilation.
          • -
          • Added new project setting to control the Renderscript compilation target separately +
          • Added new project setting to control the RenderScript compilation target separately from an Android project. Adding the following line to a {@code project.properties} - file causes Renderscript code to be compiled for Android API Level 17, while the + file causes RenderScript code to be compiled for Android API Level 17, while the containing application can target a different (lower) API level:
            renderscript.target = 17
            - Previously, the Renderscript compilation target was tied to the + Previously, the RenderScript compilation target was tied to the {@code android:minSdkVersion} setting in the manifest. (Issue 40487)
          • @@ -483,7 +565,7 @@ with GPU acceleration.
          • Improved resize algorithm for better rendering on scaled emulator windows.
          • Fixed a bug in the {@code lint} check for unprotected broadcast receivers to ignore unprotected receivers for default Android actions.
          • -
          • Fixed build issue for projects using Renderscript.
          • +
          • Fixed build issue for projects using RenderScript.
          • Fixed memory leak in the emulator.
        • @@ -823,7 +905,7 @@ ignore attribute. (Fixed emulator crash on Linux due to improper webcam detection (Issue 20952).
        • Fixed emulator issue when using the -wipe-data argument.
        • -
        • Fixed build issue when using Renderscript in projects that target API levels 11-13 +
        • Fixed build issue when using RenderScript in projects that target API levels 11-13 (Issue 21006).
        • Fixed issue when creating an AVD using the GoogleTV addon (Issue 20963).
        • diff --git a/docs/html/tools/support-library/features.jd b/docs/html/tools/support-library/features.jd index 8d25d96c91164fbc65836b6ce45eb4993a91018f..65148bf2d2ffb8873e8197566f217103a075bf10 100644 --- a/docs/html/tools/support-library/features.jd +++ b/docs/html/tools/support-library/features.jd @@ -15,6 +15,7 @@ page.title=Support Library Features
        • v7 mediarouter library
        • +
        • v8 Support Library
        • v13 Support Library
        • @@ -252,7 +253,7 @@ script dependency identifier com.android.support:support-v7-mediarouter:&l where "18.0.0" is the minimum revision at which the library is available. For example:

          -com.android.support:support-v7-mediarouter:18.0.0
          +com.android.support:mediarouter-v7:18.0.+
           

          The v7 mediarouter library APIs introduced in Support Library @@ -262,6 +263,24 @@ href="https://developers.google.com/cast/">Google Cast developer preview.

          +

          v8 Support Library

          + +

          This library is designed to be used with Android (API level 8) and higher. It adds support for + the RenderScript computation + framework. These APIs are included in the {@link android.support.v8.renderscript} package. You + should be aware that the steps for including these APIs in your application is very + different from other support library APIs. For more information about using these APIs + in your application, see the + RenderScript + developer guide.

          + +

          + Note: Use of RenderScript with the support library is supported with the Android + Eclipse plugin and Ant build tools. It is not currently supported with Android Studio or + Gradle-based builds. +

          + +

          v13 Support Library

          This library is designed to be used for Android 3.2 (API level 13) and higher. It adds support diff --git a/docs/html/tools/support-library/index.jd b/docs/html/tools/support-library/index.jd index 06c7a3ff638a2c8a6696d2fa2846ca479d99ce49..4ee8c1289fc5e26f6ed2260238771faada75b058 100644 --- a/docs/html/tools/support-library/index.jd +++ b/docs/html/tools/support-library/index.jd @@ -58,6 +58,7 @@ page.title=Support Library

          This section provides details about the Support Library package releases.

          +
        diff --git a/docs/html/training/activity-testing/activity-basic-testing.jd b/docs/html/training/activity-testing/activity-basic-testing.jd new file mode 100644 index 0000000000000000000000000000000000000000..016289d94bddd8e8265077ff8a00666e77007be3 --- /dev/null +++ b/docs/html/training/activity-testing/activity-basic-testing.jd @@ -0,0 +1,227 @@ +page.title=Creating and Running a Test Case +trainingnavtop=true + +@jd:body + + + +

        In order to verify that there are no regressions in the layout design and +functional behavior in your application, it's important to +create a test for each {@link android.app.Activity} in your application. For +each test, you need to create the individual parts of a test case, including +the test fixture, preconditions test method, and {@link android.app.Activity} +test methods. You can then run your test to get a test report. If any test +method fails, this might indicate a potential defect in your code.

        +

        Note: In the Test-Driven Development (TDD) +approach, instead of writing most or all of your app code up-front and then +running tests later in the development cycle, you would progressively write +just enough production code to satisfy your test dependencies, update your +test cases to reflect new functional requirements, and iterate repeatedly this +way.

        + +

        Create a Test Case

        +

        {@link android.app.Activity} tests are written in a structured way. +Make sure to put your tests in a separate package, distinct from the code under +test.

        +

        By convention, your test package name should follow the same name as the +application package, suffixed with ".tests". In the test package +you created, add the Java class for your test case. By convention, your test case +name should also follow the same name as the Java or Android class that you +want to test, but suffixed with “Testâ€.

        +

        To create a new test case in Eclipse:

        +
          +
        1. In the Package Explorer, right-click on the {@code /src} directory for +your test project and select New > Package.
        2. +
        3. Set the Name field to +{@code <your_app_package_name>.tests} (for example, +{@code com.example.android.testingfun.tests}) and click +Finish.
        4. +
        5. Right-click on the test package you created, and select +New > Class.
        6. +
        7. Set the Name field to +{@code <your_app_activity_name>Test} (for example, +{@code MyFirstTestActivityTest}) and click Finish.
        8. +
        + +

        Set Up Your Test Fixture

        +

        A test fixture consists of objects that must be initialized for +running one or more tests. To set up the test fixture, you can override the +{@link junit.framework.TestCase#setUp()} and +{@link junit.framework.TestCase#tearDown()} methods in your test. The +test runner automatically runs {@link junit.framework.TestCase#setUp()} before +running any other test methods, and {@link junit.framework.TestCase#tearDown()} +at the end of each test method execution. You can use these methods to keep +the code for test initialization and clean up separate from the tests methods. +

        +

        To set up your test fixture in Eclipse:

        +
          +
        1. In the Package Explorer, double-click on the test case that you created +earlier to bring up the Eclipse Java editor, then modify your test case class +to extend one of the sub-classes of {@link android.test.ActivityTestCase}. +

          For example:

          +
          +public class MyFirstTestActivityTest
          +        extends ActivityInstrumentationTestCase2<MyFirstTestActivity> {
          +
          +
        2. +
        3. Next, add the constructor and {@link junit.framework.TestCase#setUp()} +methods to your test case, and add variable declarations for the +{@link android.app.Activity} that you want to test.

          +

          For example:

          +
          +public class MyFirstTestActivityTest
          +        extends ActivityInstrumentationTestCase2<MyFirstTestActivity> {
          +
          +    private MyFirstTestActivity mFirstTestActivity;
          +    private TextView mFirstTestText;
          +
          +    public MyFirstTestActivityTest() {
          +        super(MyFirstTestActivity.class);
          +    }
          +
          +    @Override
          +    protected void setUp() throws Exception {
          +        super.setUp();
          +        mFirstTestActivity = getActivity();
          +        mFirstTestText =
          +                (TextView) mFirstTestActivity
          +                .findViewById(R.id.my_first_test_text_view);
          +    }
          +}
          +
          +

          The constructor is invoked by the test runner to instantiate the test +class, while the {@link junit.framework.TestCase#setUp()} method is invoked by +the test runner before it runs any tests in the test class.

          +
        4. +
        + +

        Typically, in the {@link junit.framework.TestCase#setUp()} method, you +should:

        +
          +
        • Invoke the superclass constructor for +{@link junit.framework.TestCase#setUp()}, which is required by JUnit.
        • +
        • Initialize your test fixture state by: +
            +
          • Defining the instance variables that store the state of the fixture.
          • +
          • Creating and storing a reference to an instance of the +{@link android.app.Activity} under test.
          • +
          • Obtaining a reference to any UI components in the +{@link android.app.Activity} that you want to test.
          • +
          +
        + +

        You can use the +{@link android.test.ActivityInstrumentationTestCase2#getActivity()} method to +get a reference to the {@link android.app.Activity} under test.

        + +

        Add Test Preconditions

        +

        As a sanity check, it is good practice to verify that the test fixture has +been set up correctly, and the objects that you want to test have been correctly +instantiated or initialized. That way, you won’t have to see +tests failing because something was wrong with the setup of your test fixture. +By convention, the method for verifying your test fixture is called +{@code testPreconditions()}.

        + +

        For example, you might want to add a {@code testPreconditons()} method like +this to your test case:

        + +
        +public void testPreconditions() {
        +    assertNotNull(“mFirstTestActivity is nullâ€, mFirstTestActivity);
        +    assertNotNull(“mFirstTestText is nullâ€, mFirstTestText);
        +}
        +
        + +

        The assertion methods are from the JUnit {@link junit.framework.Assert} +class. Generally, you can use assertions to +verify if a specific condition that you want to test is true. +

          +
        • If the condition is false, the assertion method throws an +{@link android.test.AssertionFailedError} exception, which is then typically +reported by the test runner. You can provide a string in the first argument of +your assertion method to give some contextual details if the assertion fails.
        • +
        • If the condition is true, the test passes.
        • +
        +

        In both cases, the test runner proceeds to run the other test methods in the +test case.

        + +

        Add Test Methods to Verify Your Activity

        +

        Next, add one or more test methods to verify the layout and functional +behavior of your {@link android.app.Activity}.

        +

        For example, if your {@link android.app.Activity} includes a +{@link android.widget.TextView}, you can add a test method like this to check +that it has the correct label text:

        +
        +public void testMyFirstTestTextView_labelText() {
        +    final String expected =
        +            mFirstTestActivity.getString(R.string.my_first_test);
        +    final String actual = mFirstTestText.getText().toString();
        +    assertEquals(expected, actual);
        +}
        +
        + +

        The {@code testMyFirstTestTextView_labelText()} method simply checks that the +default text of the {@link android.widget.TextView} that is set by the layout +is the same as the expected text defined in the {@code strings.xml} resource.

        +

        Note: When naming test methods, you can use +an underscore to separate what is being tested from the specific case being +tested. This style makes it easier to see exactly what cases are being tested.

        +

        When doing this type of string value comparison, it’s good practice to read +the expected string from your resources, instead of hardcoding the string in +your comparison code. This prevents your test from easily breaking whenever the +string definitions are modified in the resource file.

        +

        To perform the comparison, pass both the expected and actual strings as +arguments to the +{@link junit.framework.Assert#assertEquals(java.lang.String, java.lang.String) assertEquals()} +method. If the values are not the same, the assertion will throw an +{@link junit.framework.AssertionFailedError} exception.

        +

        If you added a {@code testPreconditions()} method, put your test methods +after the {@code testPreconditions()} definition in your Java class.

        +

        For a complete test case example, take a look at +{@code MyFirstTestActivityTest.java} in the sample app.

        + +

        Build and Run Your Test

        +

        You can build and run your test easily from the Package Explorer in +Eclipse.

        +

        To build and run your test:

        +
          +
        1. Connect an Android device to your machine. On the device or emulator, open +the Settings menu, select Developer options +and make sure that USB debugging is enabled.
        2. +
        3. In the Project Explorer, right-click on the test class that you created +earlier and select Run As > Android Junit Test.
        4. +
        5. In the Android Device Chooser dialog, select the device that you just +connected, then click OK.
        6. +
        7. In the JUnit view, verify that the test passes with no errors or failures.
        8. +
        +

        For example, if the test case passes with no errors, the result should look +like this:

        + +

        + Figure 1. Result of a test with no errors. +

        + + + diff --git a/docs/html/training/activity-testing/activity-functional-testing.jd b/docs/html/training/activity-testing/activity-functional-testing.jd new file mode 100644 index 0000000000000000000000000000000000000000..7c8ff1d7bdd7c96892d4f2d0095069b93279d13a --- /dev/null +++ b/docs/html/training/activity-testing/activity-functional-testing.jd @@ -0,0 +1,166 @@ +page.title=Creating Functional Tests +trainingnavtop=true +@jd:body + + +
        +
        + +

        This lesson teaches you to

        +
          +
        1. Add Test Method to Validate Functional Behavior +
            +
          1. Set Up an ActivityMonitor
          2. +
          3. Send Keyboard Input Using Instrumentation
          4. +
          +
        2. +
        + +

        Try it out

        +
        + Download the demo +

        AndroidTestingFun.zip

        +
        + +
        +
        +

        Functional testing involves verifying that individual application +components work together as expected by the user. For example, you can create a +functional test to verify that an {@link android.app.Activity} correctly +launches a target {@link android.app.Activity} when the user performs a UI +interaction.

        + +

        To create a functional test for your {@link android.app.Activity}, your test +class should extend {@link android.test.ActivityInstrumentationTestCase2}. +Unlike {@link android.test.ActivityUnitTestCase}, +tests in {@link android.test.ActivityInstrumentationTestCase2} can +communicate with the Android system and send keyboard input and click events to +the UI.

        + +

        For a complete test case example, take a look at +{@code SenderActivityTest.java} in the sample app.

        + +

        Add Test Method to Validate Functional Behavior

        +

        Your functional testing goals might include:

        +
          +
        • Verifying that a target {@link android.app.Activity} is started when a +UI control is pushed in the sender {@link android.app.Activity}.
        • +
        • Verifying that the target {@link android.app.Activity} displays the +correct data based on the user's input in the sender +{@link android.app.Activity}.
        • +
        +

        You might implement your test method like this:

        + +
        +@MediumTest
        +public void testSendMessageToReceiverActivity() {
        +    final Button sendToReceiverButton = (Button) 
        +            mSenderActivity.findViewById(R.id.send_message_button);
        +
        +    final EditText senderMessageEditText = (EditText) 
        +            mSenderActivity.findViewById(R.id.message_input_edit_text);
        +
        +    // Set up an ActivityMonitor
        +    ...
        +
        +    // Send string input value
        +    ...
        +
        +    // Validate that ReceiverActivity is started
        +    ...
        +
        +    // Validate that ReceiverActivity has the correct data
        +    ...
        +
        +    // Remove the ActivityMonitor
        +    ...
        +}
        +
        +

        The test waits for an {@link android.app.Activity} that matches this monitor, +otherwise returns null after a timeout elapses. If {@code ReceiverActivity} was +started, the {@link android.app.Instrumentation.ActivityMonitor ActivityMonitor} +that you set +up earlier receives a hit. You can use the assertion methods to verify that +the {@code ReceiverActivity} is indeed started, and that the hit count on the +{@link android.app.Instrumentation.ActivityMonitor ActivityMonitor} incremented +as expected.

        + +

        Set up an ActivityMonitor

        +

        To monitor a single {@link android.app.Activity} in your application, you +can register an {@link android.app.Instrumentation.ActivityMonitor ActivityMonitor}. +The {@link android.app.Instrumentation.ActivityMonitor ActivityMonitor} is +notified by the system whenever an {@link android.app.Activity} that matches your criteria is started. +If a match is found, the monitor’s hit count is updated.

        +

        Generally, to use an +{@link android.app.Instrumentation.ActivityMonitor ActivityMonitor}, you should:

        +
          +
        1. Retrieve the {@link android.app.Instrumentation} instance for your test +case by using the +{@link android.test.InstrumentationTestCase#getInstrumentation()} method.
        2. +
        3. Add an instance of {@link android.app.Instrumentation.ActivityMonitor} to +the current instrumentation using one of the {@link android.app.Instrumentation} +{@code addMonitor()} methods. The match criteria can be specified as an +{@link android.content.IntentFilter} or a class name string.
        4. +
        5. Wait for the {@link android.app.Activity} to start.
        6. +
        7. Verify that the monitor hits were incremented.
        8. +
        9. Remove the monitor.
        10. +
        +

        For example:

        +
        +// Set up an ActivityMonitor
        +ActivityMonitor receiverActivityMonitor =
        +        getInstrumentation().addMonitor(ReceiverActivity.class.getName(),
        +        null, false);
        +
        +// Validate that ReceiverActivity is started
        +TouchUtils.clickView(this, sendToReceiverButton);
        +ReceiverActivity receiverActivity = (ReceiverActivity) 
        +        receiverActivityMonitor.waitForActivityWithTimeout(TIMEOUT_IN_MS);
        +assertNotNull("ReceiverActivity is null", receiverActivity);
        +assertEquals("Monitor for ReceiverActivity has not been called",
        +        1, receiverActivityMonitor.getHits());
        +assertEquals("Activity is of wrong type",
        +        ReceiverActivity.class, receiverActivity.getClass());
        +
        +// Remove the ActivityMonitor
        +getInstrumentation().removeMonitor(receiverActivityMonitor);
        +
        + +

        Send Keyboard Input Using Instrumentation

        +

        If your {@link android.app.Activity} has an {@link android.widget.EditText} +field, you might want to test that users can enter values into the +{@link android.widget.EditText} object.

        +

        Generally, to send a string input value to an {@link android.widget.EditText} +object in {@link android.test.ActivityInstrumentationTestCase2}, you should:

        +
          +
        1. Use the {@link android.app.Instrumentation#runOnMainSync(java.lang.Runnable) runOnMainSync()} +method to run the {@link android.view.View#requestFocus()} call synchronously +in a loop. This way, the UI thread is blocked until focus is received.
        2. +
        3. Call {@link android.app.Instrumentation#waitForIdleSync()} method to wait +for the main thread to become idle (that is, have no more events to process).
        4. +
        5. Send a text string to the {@link android.widget.EditText} by calling +{@link android.app.Instrumentation#sendStringSync(java.lang.String) +sendStringSync()} and pass your input string as the parameter.

          +
        +

        For example:

        +
        +// Send string input value
        +getInstrumentation().runOnMainSync(new Runnable() {
        +    @Override
        +    public void run() {
        +        senderMessageEditText.requestFocus();
        +    }
        +});
        +getInstrumentation().waitForIdleSync();
        +getInstrumentation().sendStringSync("Hello Android!");
        +getInstrumentation().waitForIdleSync();
        +
        + + + + + + + + diff --git a/docs/html/training/activity-testing/activity-ui-testing.jd b/docs/html/training/activity-testing/activity-ui-testing.jd new file mode 100644 index 0000000000000000000000000000000000000000..644f3ca5151329a49ebacbfe3cf0a92f96c85e46 --- /dev/null +++ b/docs/html/training/activity-testing/activity-ui-testing.jd @@ -0,0 +1,216 @@ +page.title=Testing UI Components +trainingnavtop=true + +@jd:body + + + + +

        Typically, your {@link android.app.Activity} includes user interface +components (such as buttons, editable text fields, checkboxes, and pickers) to +allow users to interact with your Android application. This lesson shows how +you can test an {@link android.app.Activity} with a simple push-button UI. You +can use the same general steps to test other, more sophisticated types of UI +components.

        + +

        Note: The type of UI testing in this lesson is +called white-box testing because you have the +source code for the application that you want to test. The Android +Instrumentation +framework is suitable for creating white-box tests for UI components within an +application. An alternative type of UI testing is black-box testing, +where you may not have access to the application source. This type of testing +is useful when you want to test how your app interacts with other apps or with +the system. Black-box testing is not covered in this training. To learn more +about how to perform black-box testing on your Android apps, see the +UI Testing guide. +

        For a complete test case example, take a look at +{@code ClickFunActivityTest.java} in the sample app.

        + +

        Create a Test Case for UI Testing with Instrumentation

        +

        When testing an {@link android.app.Activity} that has a user interface (UI), +the {@link android.app.Activity} under test runs in the UI thread. However, the +test application itself runs in a separate thread in the same process as the +application under test. This means that your test app can reference objects +from the UI thread, but if it attempts to change properties on those objects or +send events to the UI thread, you will usually get a {@code WrongThreadException} +error.

        +

        To safely inject {@link android.content.Intent} objects into your +{@link android.app.Activity} or run test methods on the UI thread, you can +extend your test class to use {@link android.test.ActivityInstrumentationTestCase2}. +To learn more about how to run test methods on the UI thread, see +Testing +on the UI thread.

        + +

        Set Up Your Test Fixture

        +

        When setting up the test fixture for UI testing, you should specify the +touch mode +in your {@link junit.framework.TestCase#setUp()} method. Setting the touch mode +to {@code true} prevents the UI control from taking focus when you click it +programmatically in the test method later (for example, a button UI will just +fire its on-click listener). Make sure that you call +{@link android.test.ActivityInstrumentationTestCase2#setActivityInitialTouchMode(boolean) setActivityInitialTouchMode()} +before calling {@link android.test.ActivityInstrumentationTestCase2#getActivity()}. +

        +

        For example: +

        +public class ClickFunActivityTest
        +        extends ActivityInstrumentationTestCase2 {
        +    ...
        +    @Override
        +    protected void setUp() throws Exception {
        +        super.setUp();
        +
        +        setActivityInitialTouchMode(true);
        +
        +        mClickFunActivity = getActivity();
        +        mClickMeButton = (Button) 
        +                mClickFunActivity
        +                .findViewById(R.id.launch_next_activity_button);
        +        mInfoTextView = (TextView) 
        +                mClickFunActivity.findViewById(R.id.info_text_view);
        +    }
        +}
        +
        + +

        Add Test Methods to Validate UI Behavior

        +

        Your UI testing goals might include:

        +
          +
        • Verifying that a button is displayed with the correct layout when the +{@link android.app.Activity} is launched.
        • +
        • Verifying that a {@link android.widget.TextView} is initially hidden.
        • +
        • Verifying that a {@link android.widget.TextView} displays the expected string +when a button is pushed.
        • +
        +

        The following section demonstrates how you can implement test methods +to perform these verifications.

        + +

        Verify Button Layout Parameters

        +

        You might add a test method like this to verify that a button is displayed +correctly in your {@link android.app.Activity}:

        +
        +@MediumTest
        +public void testClickMeButton_layout() {
        +    final View decorView = mClickFunActivity.getWindow().getDecorView();
        +
        +    ViewAsserts.assertOnScreen(decorView, mClickMeButton);
        +
        +    final ViewGroup.LayoutParams layoutParams =
        +            mClickMeButton.getLayoutParams();
        +    assertNotNull(layoutParams);
        +    assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        +    assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
        +}
        +
        + +

        In the {@link android.test.ViewAsserts#assertOnScreen(android.view.View,android.view.View) assertOnScreen()} +method call, you should pass in the root view and the view that you are +expecting to be present on the screen. If the expected view is not found in the +root view, the assertion method throws an {@link junit.framework.AssertionFailedError} +exception, otherwise the test passes.

        +

        You can also verify that the layout of a {@link android.widget.Button} is +correct by getting a reference to its {@link android.view.ViewGroup.LayoutParams} +object, then call assertion methods to verify that the +{@link android.widget.Button} object's width and height attributes match the +expected values.

        +

        The {@code @MediumTest} annotation specifies how the test is categorized, +relative to its absolute execution time. To learn more about using test size +annotations, see Apply Test Annotations.

        + +

        Verify TextView Layout Parameters

        +

        You might add a test method like this to verify that a +{@link android.widget.TextView} initially appears hidden in +your {@link android.app.Activity}:

        +
        +@MediumTest
        +public void testInfoTextView_layout() {
        +    final View decorView = mClickFunActivity.getWindow().getDecorView();
        +    ViewAsserts.assertOnScreen(decorView, mInfoTextView);
        +    assertTrue(View.GONE == mInfoTextView.getVisibility());
        +}
        +
        +

        You can call {@link android.view.Window#getDecorView()} to get a reference +to the decor view for the {@link android.app.Activity}. The decor view is the +top-level ViewGroup ({@link android.widget.FrameLayout}) view in the layout +hierarchy.

        + +

        Verify Button Behavior

        +

        You can use a test method like this to verify that a +{@link android.widget.TextView} becomes visible when a +{@link android.widget.Button} is pushed:

        + +
        +@MediumTest
        +public void testClickMeButton_clickButtonAndExpectInfoText() {
        +    String expectedInfoText = mClickFunActivity.getString(R.string.info_text);
        +    TouchUtils.clickView(this, mClickMeButton);
        +    assertTrue(View.VISIBLE == mInfoTextView.getVisibility());
        +    assertEquals(expectedInfoText, mInfoTextView.getText());
        +}
        +
        + +

        To programmatically click a {@link android.widget.Button} in your +test, call {@link android.test.TouchUtils#clickView(android.test.InstrumentationTestCase,android.view.View) clickView()}. +You must pass in a reference to the test case that is being run and a reference +to the {@link android.widget.Button} to manipulate.

        + +

        Note: The {@link android.test.TouchUtils} +helper class provides convenience methods for simulating touch interactions +with your application. You can use these methods to simulate clicking, tapping, +and dragging of Views or the application screen.

        +

        Caution: The {@link android.test.TouchUtils} +methods are designed to send events to the UI thread safely from the test thread. +You should not run {@link android.test.TouchUtils} directly in the UI thread or +any test method annotated with {@code @UIThread}. Doing so might +raise the {@code WrongThreadException}.

        + +

        Apply Test Annotations

        +

        The following annotations can be applied to indicate the size of a test +method:

        +
        +
        {@link +android.test.suitebuilder.annotation.SmallTest @SmallTest}
        +
        Marks a test that should run as part of the small tests.
        +
        {@link +android.test.suitebuilder.annotation.MediumTest @MediumTest}
        +
        Marks a test that should run as part of the medium tests.
        +
        {@link android.test.suitebuilder.annotation.LargeTest @LargeTest}
        +
        Marks a test that should run as part of the large tests.
        +
        +

        Typically, a short running test that take only a few milliseconds should be +marked as a {@code @SmallTest}. Longer running tests (100 milliseconds or +more) are usually marked as {@code @MediumTest}s or {@code @LargeTest}s, +depending on whether the test accesses resources on the local system only or +remote resources over a network. For guidance on using test size annotations, +see this Android Tools Protip.

        +

        You can mark up your test methods with other test annotations to control +how the tests are organized and run. For more information on other annotations, +see the {@link java.lang.annotation.Annotation} class reference.

        + + + + diff --git a/docs/html/training/activity-testing/activity-unit-testing.jd b/docs/html/training/activity-testing/activity-unit-testing.jd new file mode 100644 index 0000000000000000000000000000000000000000..74dcda90b18d46d90e34b9f87b0651e9b7148af8 --- /dev/null +++ b/docs/html/training/activity-testing/activity-unit-testing.jd @@ -0,0 +1,134 @@ +page.title=Creating Unit Tests +trainingnavtop=true +@jd:body + + +
        +
        + +

        This lesson teaches you to

        +
          +
        1. Create a Test Case for Activity Unit Testing +
        2. Validate Launch of Another Activity +
        + +

        Try it out

        +
        + Download the demo +

        AndroidTestingFun.zip

        +
        + +
        +
        + +

        An {@link android.app.Activity} unit test is an excellent way to quickly +verify the state of an {@link android.app.Activity} and its interactions with +other components in isolation (that is, disconnected from the rest of the +system). A unit test generally tests the smallest possible unit of code +(which could be a method, class, or component), without dependencies on system +or network resources. For example, you can write a unit test to check +that an {@link android.app.Activity} has the correct layout or that it +triggers an {@link android.content.Intent} object correctly.

        +

        Unit tests are generally not suitable for testing complex UI interaction +events with the system. Instead, you should use +the {@link android.test.ActivityInstrumentationTestCase2} class, as described +in Testing UI Components.

        +

        This lesson shows how you can write a unit test to verify that an +{@link android.content.Intent} is triggered to launch another +{@link android.app.Activity}. +Since the test runs in an isolated environment, the +{@link android.content.Intent} +is not actually sent to the Android system, but you can inspect that the +{@link android.content.Intent} object's payload data is accurate.

        +

        For a complete test case example, take a look at +{@code LaunchActivityTest.java} in the sample app.

        + +

        Note: To test against system or external +dependencies, you can use mock objects from a mocking +framework and inject them into your unit tests. To learn more about the mocking +framework provided by Android, see +Mock +Object Classes.

        + +

        Create a Test Case for Activity Unit Testing

        +

        The {@link android.test.ActivityUnitTestCase} class provides support for +isolated testing of a single {@link android.app.Activity}. To create a unit +test for your {@link android.app.Activity}, your test class should extend +{@link android.test.ActivityUnitTestCase}.

        + +

        The {@link android.app.Activity} in an {@link android.test.ActivityUnitTestCase} +is not automatically started by Android Instrumentation. To start the +{@link android.app.Activity} in isolation, you need to explicitly call the +{@link android.test.ActivityUnitTestCase#startActivity(android.content.Intent, android.os.Bundle, java.lang.Object) startActivity()} +method, and pass in the {@link android.content.Intent} to +launch your target {@link android.app.Activity}.

        + +

        For example:

        +
        +public class LaunchActivityTest
        +        extends ActivityUnitTestCase<LaunchActivity> {
        +    ...
        +
        +    @Override
        +    protected void setUp() throws Exception {
        +        super.setUp();
        +        mLaunchIntent = new Intent(getInstrumentation()
        +                .getTargetContext(), LaunchActivity.class);
        +        startActivity(mLaunchIntent, null, null);
        +        final Button launchNextButton =
        +                (Button) getActivity()
        +                .findViewById(R.id.launch_next_activity_button);
        +    }
        +}
        +
        + +

        Validate Launch of Another Activity

        +

        Your unit testing goals might include:

        +
          +
        • Verifying that {@code LaunchActivity} fires an +{@link android.content.Intent} when a button is pushed clicked.
        • +
        • Verifying that the launched {@link android.content.Intent} contains the +correct payload data.
        • +
        + +

        To verify if an {@link android.content.Intent} was triggered +following the {@link android.widget.Button} click, you can use the +{@link android.test.ActivityUnitTestCase#getStartedActivityIntent()} method. +By using assertion methods, you can verify that the returned +{@link android.content.Intent} is not null, and that it contains the expected +string value to launch the next {@link android.app.Activity}. If both assertions +evaluate to {@code true}, you've successfully verified that the +{@link android.content.Intent} was correctly sent by your +{@link android.app.Activity}.

        + +

        You might implement your test method like this:

        +
        +@MediumTest
        +public void testNextActivityWasLaunchedWithIntent() {
        +    startActivity(mLaunchIntent, null, null);
        +    final Button launchNextButton =
        +            (Button) getActivity()
        +            .findViewById(R.id.launch_next_activity_button);
        +    launchNextButton.performClick();
        +
        +    final Intent launchIntent = getStartedActivityIntent();
        +    assertNotNull("Intent was null", launchIntent);
        +    assertTrue(isFinishCalled());
        +
        +    final String payload =
        +            launchIntent.getStringExtra(NextActivity.EXTRAS_PAYLOAD_KEY);
        +    assertEquals("Payload is empty", LaunchActivity.STRING_PAYLOAD, payload);
        +}
        +
        +

        Because {@code LaunchActivity} runs in isolation, you cannot use the +{@link android.test.TouchUtils} library to manipulate UI controls. To directly +click a {@link android.widget.Button}, you can call the +{@link android.view.View#performClick()} method instead.

        + + + + + + + diff --git a/docs/html/training/activity-testing/index.jd b/docs/html/training/activity-testing/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..ddede712d83ee05d04275f88c76e1fc0d47098eb --- /dev/null +++ b/docs/html/training/activity-testing/index.jd @@ -0,0 +1,68 @@ +page.title=Testing Your Android Activity +page.tags="testing" + +trainingnavtop=true +startpage=true + +@jd:body + +
        +
        + + +

        Dependencies and prerequisites

        +
          +
        • Android 2.2 (API Level 8) or higher.
        • +
        + +

        You Should Also Read

        + + +
        +
        + +

        You should be writing and running tests as part of your Android application +development cycle. Well-written tests can help you to catch bugs early in +development and give you confidence in your code.

        + +

        A test case defines a set of objects and methods to run multiple +tests independently from each other. Test cases can be organized into +test suites and run programmatically, in a repeatable manner, with +a test runner provided by a testing framework.

        + +

        The lessons in this class teaches you how to use the Android's custom +testing framework that is based on the popular JUnit framework. You can +write test cases to verify specific behavior in your application, and check for +consistency across different Android devices. Your test cases also serve as a +form of internal code documentation by describing the expected behavior of +app components.

        + +

        Lessons

        + + + +
        +
        Setting Up Your Test +Environment
        +
        Learn how to create your test project.
        +
        Creating and Running a Test +Case
        +
        Learn how to write test cases to verify the +expected properties of your {@link android.app.Activity}, and run the test +cases with the {@code Instrumentation} test runner provided by the Android +framework.
        +
        Testing UI Components
        +
        Learn how to test the behavior of specific UI +components in your {@link android.app.Activity}.
        +
        Creating Unit Tests
        +
        Learn how to how to perform unit testing to +verify the behavior of an Activity in isolation.
        +
        Creating Functional Tests
        +
        Learn how to perform functional testing to +verify the interaction of multiple Activities.
        + diff --git a/docs/html/training/activity-testing/preparing-activity-testing.jd b/docs/html/training/activity-testing/preparing-activity-testing.jd new file mode 100644 index 0000000000000000000000000000000000000000..c43c9ed4a30426613f385e479a3c68bc36f9746b --- /dev/null +++ b/docs/html/training/activity-testing/preparing-activity-testing.jd @@ -0,0 +1,95 @@ +page.title=Setting Up Your Test Environment +trainingnavtop=true + +@jd:body + + +
        +
        + +

        This lesson teaches you to

        +
          +
        1. Set Up Eclipse for Testing
        2. +
        3. Set Up the Command Line Interface for Testing
        4. +
        + +

        You should also read

        + + +

        Try it out

        +
        + Download the demo +

        AndroidTestingFun.zip

        +
        + +
        +
        + +

        Before you start writing and running your tests, you should set up your test +development environment. This lesson teaches you how to set up the Eclipse +IDE to build and run tests, and how to +build and run tests with the Gradle framework by using the command line +interface.

        + +

        Note: To help you get started, the lessons are +based on Eclipse with the ADT plugin. However, for your own test development, you +are free to use the IDE of your choice or the command-line.

        + +

        Set Up Eclipse for Testing

        +

        Eclipse with the Android Developer Tools (ADT) plugin provides an integrated +development environment for you to create, build, and run Android application +test cases from a graphical user interface (GUI). A convenient feature that +Eclipse provides is the ability to auto-generate a new test project that +corresponds with your Android application project. + +

        To set up your test environment in Eclipse:

        + +
          +
        1. Download and install the +Eclipse ADT plugin, if you haven’t installed it yet.
        2. +
        3. Import or create the Android application project that you want to test +against.
        4. +
        5. Generate a test project that corresponds to the application project under +test. To generate a test project for the app project that you imported:

          +
            +
          1. In the Package Explorer, right-click on your app project, then +select Android Tools > New Test Project.
          2. +
          3. In the New Android Test Project wizard, set the property +values for your test project then click Finish.
          4. +
          +
        6. +
        +

        You should now be able to create, build, and run test +cases from your Eclipse environment. To learn how to perform these tasks in +Eclipse, proceed to Creating and Running +a Test Case.

        + +

        Set Up the Command Line Interface for Testing

        +

        If you are using Gradle version 1.6 or higher as your build environment, you +can build and run your Android application tests from the command line by using +the Gradle Wrapper. Make sure that in your {@code gradle.build} file, the +minSdkVersion +attribute in the {@code defaultConfig} section is set to 8 or higher. You can +refer to the sample {@code gradle.build} file that is +included in the download bundle for this training class.

        +

        To run your tests with the Gradle Wrapper:

        +
          +
        1. Connect a physical Android device to your machine or launch the Android +Emulator.
        2. +
        3. Run the following command from your project directory: +
          ./gradlew build connectedCheck
          +
        4. +
        +

        To learn more about using Gradle for Android testing, see the +Gradle Plugin User Guide.

        +

        To learn more about using command line tools other than Gradle for test +development, see +Testing from Other IDEs.

        + diff --git a/docs/html/training/animation/crossfade.jd b/docs/html/training/animation/crossfade.jd index 2fbb6c00b45b61289d98005207edeab7caada859..7e947f3d7ece5567270ae973f00f8508d542ceff 100644 --- a/docs/html/training/animation/crossfade.jd +++ b/docs/html/training/animation/crossfade.jd @@ -205,13 +205,13 @@ private void crossfade() { // Animate the loading view to 0% opacity. After the animation ends, // set its visibility to GONE as an optimization step (it won't // participate in layout passes, etc.) - mHideView.animate() + mLoadingView.animate() .alpha(0f) .setDuration(mShortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mHideView.setVisibility(View.GONE); + mLoadingView.setVisibility(View.GONE); } }); } diff --git a/docs/html/training/animation/index.jd b/docs/html/training/animation/index.jd index b2815fcce95a9a0e317c3e40a318e4398da05077..b6940f82221fca9282e170e5eb1a86337edb70ff 100644 --- a/docs/html/training/animation/index.jd +++ b/docs/html/training/animation/index.jd @@ -1,5 +1,5 @@ page.title=Adding Animations -page.tags="animation","views","layout","user interface" +page.tags="Animator","views","layout","user interface" trainingnavtop=true startpage=true diff --git a/docs/html/training/articles/memory.jd b/docs/html/training/articles/memory.jd new file mode 100644 index 0000000000000000000000000000000000000000..cdc0cd42d21b66b019a73e0ddf8b37c2e590c299 --- /dev/null +++ b/docs/html/training/articles/memory.jd @@ -0,0 +1,740 @@ +page.title=Managing Your App's Memory +page.tags="ram","low memory","OutOfMemoryError","onTrimMemory" +page.article=true +@jd:body + + + + + +

        Random-access memory (RAM) is a valuable resource in any software development environment, but +it's even more valuable on a mobile operating system where physical memory is often constrained. +Although Android's Dalvik virtual machine performs routine garbage collection, this doesn't allow +you to ignore when and where your app allocates and releases memory.

        + +

        In order for the garbage collector to reclaim memory from your app, you need to avoid +introducing memory leaks (usually caused by holding onto object references in global members) and +release any {@link java.lang.ref.Reference} objects at the appropriate time (as defined by +lifecycle callbacks discussed further below). For most apps, the Dalvik garbage collector takes +care of the rest: the system reclaims your memory allocations when the corresponding objects leave +the scope of your app's active threads.

        + +

        This document explains how Android manages app processes and memory allocation, and how you can +proactively reduce memory usage while developing for Android. For more information about general +practices to clean up your resources when programming in Java, refer to other books or online +documentation about managing resource references. If you’re looking for information about how to +analyze your app’s memory once you’ve already built it, read Investigating Your RAM Usage.

        + + + + +

        How Android Manages Memory

        + +

        Android does not offer swap space for memory, but it does use paging and memory-mapping +(mmapping) to manage memory. This means that any memory you modify—whether by allocating +new objects or touching mmapped pages—remains resident in RAM and cannot be paged out. +So the only way to completely release memory from your app is to release object references you may +be holding, making the memory available to the garbage collector. That is with one exception: +any files mmapped in without modification, such as code, can be paged out of RAM if the system +wants to use that memory elsewhere.

        + + +

        Sharing Memory

        + +

        In order to fit everything it needs in RAM, Android tries to share RAM pages across processes. It +can do so in the following ways:

        +
          +
        • Each app process is forked from an existing process called Zygote. +The Zygote process starts when the system boots and loads common framework code and resources +(such as activity themes). To start a new app process, the system forks the Zygote process then +loads and runs the app's code in the new process. This allows most of the RAM pages allocated for +framework code and resources to be shared across all app processes.
        • + +
        • Most static data is mmapped into a process. This not only allows that same data to be shared +between processes but also allows it to be paged out when needed. Example static data include: +Dalvik code (by placing it in a pre-linked {@code .odex} file for direct mmapping), app resources +(by designing the resource table to be a structure that can be mmapped and by aligning the zip +entries of the APK), and traditional project elements like native code in {@code .so} files.
        • + +
        • In many places, Android shares the same dynamic RAM across processes using explicitly allocated +shared memory regions (either with ashmem or gralloc). For example, window surfaces use shared +memory between the app and screen compositor, and cursor buffers use shared memory between the +content provider and client.
        • +
        + +

        Due to the extensive use of shared memory, determining how much memory your app is using requires +care. Techniques to properly determine your app's memory use are discussed in Investigating Your RAM Usage.

        + + +

        Allocating and Reclaiming App Memory

        + +

        Here are some facts about how Android allocates then reclaims memory from your app:

        + +
          +
        • The Dalvik heap for each process is constrained to a single virtual memory range. This defines +the logical heap size, which can grow as it needs to (but only up to a limit that the system defines +for each app).
        • + +
        • The logical size of the heap is not the same as the amount of physical memory used by the heap. +When inspecting your app's heap, Android computes a value called the Proportional Set Size (PSS), +which accounts for both dirty and clean pages that are shared with other processes—but only in an +amount that's proportional to how many apps share that RAM. This (PSS) total is what the system +considers to be your physical memory footprint. For more information about PSS, see the Investigating Your +RAM Usage guide.
        • + +
        • The Dalvik heap does not compact the logical size of the heap, meaning that Android does not +defragment the heap to close up space. Android can only shrink the logical heap size when there +is unused space at the end of the heap. But this doesn't mean the physical memory used by the heap +can't shrink. After garbage collection, Dalvik walks the heap and finds unused pages, then returns +those pages to the kernel using madvise. So, paired allocations and deallocations of large +chunks should result in reclaiming all (or nearly all) the physical memory used. However, +reclaiming memory from small allocations can be much less efficient because the page used +for a small allocation may still be shared with something else that has not yet been freed.
        • +
        + + +

        Restricting App Memory

        + +

        To maintain a functional multi-tasking environment, Android sets a hard limit on the heap size +for each app. The exact heap size limit varies between devices based on how much RAM the device +has available overall. If your app has reached the heap capacity and tries to allocate more +memory, it will receive an {@link java.lang.OutOfMemoryError}.

        + +

        In some cases, you might want to query the system to determine exactly how much heap space you +have available on the current device—for example, to determine how much data is safe to keep in a +cache. You can query the system for this figure by calling {@link +android.app.ActivityManager#getMemoryClass()}. This returns an integer indicating the number of +megabytes available for your app's heap. This is discussed further below, under +Check how much memory you should use.

        + + +

        Switching Apps

        + +

        Instead of using swap space when the user switches between apps, Android keeps processes that +are not hosting a foreground ("user visible") app component in a least-recently used (LRU) cache. +For example, when the user first launches an app, a process is created for it, but when the user +leaves the app, that process does not quit. The system keeps the process cached, so if +the user later returns to the app, the process is reused for faster app switching.

        + +

        If your app has a cached process and it retains memory that it currently does not need, +then your app—even while the user is not using it—is constraining the system's +overall performance. So, as the system runs low on memory, it may kill processes in the LRU cache +beginning with the process least recently used, but also giving some consideration toward +which processes are most memory intensive. To keep your process cached as long as possible, follow +the advice in the following sections about when to release your references.

        + +

        More information about how processes are cached while not running in the foreground and how +Android decides which ones +can be killed is available in the Processes and Threads guide.

        + + + + +

        How Your App Should Manage Memory

        + +

        You should consider RAM constraints throughout all phases of development, including during app +design (before you begin development). There are many +ways you can design and write code that lead to more efficient results, through aggregation of the +same techniques applied over and over.

        + +

        You should apply the following techniques while designing and implementing your app to make it +more memory efficient.

        + + +

        Use services sparingly

        + +

        If your app needs a service +to perform work in the background, do not keep it running unless +it's actively performing a job. Also be careful to never leak your service by failing to stop it +when its work is done.

        + +

        When you start a service, the system prefers to always keep the process for that service +running. This makes the process very expensive because the RAM used by the service can’t be used by +anything else or paged out. This reduces the number of cached processes that the system can keep in +the LRU cache, making app switching less efficient. It can even lead to thrashing in the system +when memory is tight and the system can’t maintain enough processes to host all the services +currently running.

        + +

        The best way to limit the lifespan of your service is to use an {@link +android.app.IntentService}, which finishes +itself as soon as it's done handling the intent that started it. For more information, read +Running in a Background Service +.

        + +

        Leaving a service running when it’s not needed is one of the worst memory-management +mistakes an Android app can make. So don’t be greedy by keeping a service for your app +running. Not only will it increase the risk of your app performing poorly due to RAM constraints, +but users will discover such misbehaving apps and uninstall them.

        + + +

        Release memory when your user interface becomes hidden

        + +

        When the user navigates to a different app and your UI is no longer visible, you should +release any resources that are used by only your UI. Releasing UI resources at this time can +significantly increase the system's capacity for cached processes, which has a direct impact on the +quality of the user experience.

        + +

        To be notified when the user exits your UI, implement the {@link +android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback in your {@link +android.app.Activity} classes. You should use this +method to listen for the {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN} level, +which indicates your UI is now hidden from view and you should free resources that only your UI +uses.

        + + +

        Notice that your app receives the {@link android.content.ComponentCallbacks2#onTrimMemory +onTrimMemory()} callback with {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN} +only when all the UI components of your app process become hidden from the user. +This is distinct +from the {@link android.app.Activity#onStop onStop()} callback, which is called when an {@link +android.app.Activity} instance becomes hidden, which occurs even when the user moves to +another activity in your app. So although you should implement {@link android.app.Activity#onStop +onStop()} to release activity resources such as a network connection or to unregister broadcast +receivers, you usually should not release your UI resources until you receive {@link +android.content.ComponentCallbacks2#onTrimMemory onTrimMemory(TRIM_MEMORY_UI_HIDDEN)}. This ensures +that if the user navigates back from another activity in your app, your UI resources are +still available to resume the activity quickly.

        + + + +

        Release memory as memory becomes tight

        + +

        During any stage of your app's lifecycle, the {@link +android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback also tells you when +the overall device memory is getting low. You should respond by further releasing resources based +on the following memory levels delivered by {@link android.content.ComponentCallbacks2#onTrimMemory +onTrimMemory()}:

        + +
          +
        • {@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_MODERATE} +

          Your app is running and not considered killable, but the device is running low on memory and the +system is actively killing processes in the LRU cache.

          +
        • + +
        • {@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_LOW} +

          Your app is running and not considered killable, but the device is running much lower on +memory so you should release unused resources to improve system performance (which directly +impacts your app's performance).

          +
        • + +
        • {@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_CRITICAL} +

          Your app is still running, but the system has already killed most of the processes in the +LRU cache, so you should release all non-critical resources now. If the system cannot reclaim +sufficient amounts of RAM, it will clear all of the LRU cache and begin killing processes that +the system prefers to keep alive, such as those hosting a running service.

          +
        • +
        + +

        Also, when your app process is currently cached, you may receive one of the following +levels from {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()}:

        +
          +
        • {@link android.content.ComponentCallbacks2#TRIM_MEMORY_BACKGROUND} +

          The system is running low on memory and your process is near the beginning of the LRU list. +Although your app process is not at a high risk of being killed, the system may already be killing +processes in the LRU cache. You should release resources that are easy to recover so your process +will remain in the list and resume quickly when the user returns to your app.

          +
        • + +
        • {@link android.content.ComponentCallbacks2#TRIM_MEMORY_MODERATE} +

          The system is running low on memory and your process is near the middle of the LRU list. If the +system becomes further constrained for memory, there's a chance your process will be killed.

          +
        • + +
        • {@link android.content.ComponentCallbacks2#TRIM_MEMORY_COMPLETE} +

          The system is running low on memory and your process is one of the first to be killed if the +system does not recover memory now. You should release everything that's not critical to +resuming your app state.

          + +
        • +
        + +

        Because the {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback was +added in API level 14, you can use the {@link android.content.ComponentCallbacks#onLowMemory()} +callback as a fallback for older versions, which is roughly equivalent to the {@link +android.content.ComponentCallbacks2#TRIM_MEMORY_COMPLETE} event.

        + +

        Note: When the system begins killing processes in the LRU cache, +although it primarily works bottom-up, it does give some consideration to which processes are +consuming more memory and will thus provide the system more memory gain if killed. +So the less memory you consume while in the LRU list overall, the better your chances are +to remain in the list and be able to quickly resume.

        + + + +

        Check how much memory you should use

        + +

        As mentioned earlier, each Android-powered device has a different amount of RAM available to the +system and thus provides a different heap limit for each app. You can call {@link +android.app.ActivityManager#getMemoryClass()} to get an estimate of your app's available heap in +megabytes. If your app tries to allocate more memory than is available here, it will receive an +{@link java.lang.OutOfMemoryError}.

        + +

        In very special situations, you can request a larger heap size by setting the {@code largeHeap} +attribute to "true" in the manifest {@code <application>} +tag. If you do so, you can call {@link +android.app.ActivityManager#getLargeMemoryClass()} to get an estimate of the large heap size.

        + +

        However, the ability to request a large heap is intended only for a small set of apps that can +justify the need to consume more RAM (such as a large photo editing app). Never request a +large heap simply because you've run out of memory and you need a quick fix—you +should use it only when you know exactly where all your memory is being allocated and why it must +be retained. Yet, even when you're confident your app can justify the large heap, you should avoid +requesting it to whatever extent possible. Using the extra memory will increasingly be to the +detriment of the overall user experience because garbage collection will take longer and system +performance may be slower when task switching or performing other common operations.

        + +

        Additionally, the large heap size is not the same on all devices and, when running on +devices that have limited RAM, the large heap size may be exactly the same as the regular heap +size. So even if you do request the large heap size, you should call {@link +android.app.ActivityManager#getMemoryClass()} to check the regular heap size and strive to always +stay below that limit.

        + + +

        Avoid wasting memory with bitmaps

        + +

        When you load a bitmap, keep it in RAM only at the resolution you need for the current device's +screen, scaling it down if the original bitmap is a higher resolution. Keep in mind that an +increase in bitmap resolution results in a corresponding (increase2) in memory needed, +because both the X and Y dimensions increase.

        + +

        Note: On Android 2.3.x (API level 10) and below, bitmap objects +always appear as the same size in your app heap regardless of the image resolution (the actual +pixel data is stored separately in native memory). This makes it more difficult to debug the bitmap +memory allocation because most heap analysis tools do not see the native allocation. However, +beginning in Android 3.0 (API level 11), the bitmap pixel data is allocated in your app's Dalvik +heap, improving garbage collection and debuggability. So if your app uses bitmaps and you're having +trouble discovering why your app is using some memory on an older device, switch to a device +running Android 3.0 or higher to debug it.

        + +

        For more tips about working with bitmaps, read Managing Bitmap Memory.

        + + +

        Use optimized data containers

        + +

        Take advantage of optimized containers in the Android framework, such as {@link +android.util.SparseArray}, {@link android.util.SparseBooleanArray}, and {@link +android.support.v4.util.LongSparseArray}. The generic {@link java.util.HashMap} +implementation can be quite memory +inefficient because it needs a separate entry object for every mapping. Additionally, the {@link +android.util.SparseArray} classes are more efficient because they avoid the system's need +to autobox +the key and sometimes value (which creates yet another object or two per entry). And don't be +afraid of dropping down to raw arrays when that makes sense.

        + + + +

        Be aware of memory overhead

        + +

        Be knowledgeable about the cost and overhead of the language and libraries you are using, and +keep this information in mind when you design your app, from start to finish. Often, things on the +surface that look innocuous may in fact have a large amount of overhead. Examples include:

        +
          +
        • Enums often require more than twice as much memory as static constants. You should strictly +avoid using enums on Android.
        • + +
        • Every class in Java (including anonymous inner classes) uses about 500 bytes of code.
        • + +
        • Every class instance has 12-16 bytes of RAM overhead.
        • + +
        • Putting a single entry into a {@link java.util.HashMap} requires the allocation of an +additional entry object that takes 32 bytes (see the previous section about optimized data containers).
        • +
        + +

        A few bytes here and there quickly add up—app designs that are class- or object-heavy will suffer +from this overhead. That can leave you in the difficult position of looking at a heap analysis and +realizing your problem is a lot of small objects using up your RAM.

        + + +

        Be careful with code abstractions

        + +

        Often, developers use abstractions simply as a "good programming practice," because abstractions +can improve code flexibility and maintenance. However, abstractions come at a significant cost: +generally they require a fair amount more code that needs to be executed, requiring more time and +more RAM for that code to be mapped into memory. So if your abstractions aren't supplying a +significant benefit, you should avoid them.

        + + +

        Use nano protobufs for serialized data

        + +

        Protocol +buffers are a language-neutral, platform-neutral, extensible mechanism designed by Google for +serializing structured data—think XML, but smaller, faster, and simpler. If you decide to use +protobufs for your data, you should always use nano protobufs in your client-side code. Regular +protobufs generate extremely verbose code, which will cause many kinds of problems in your app: +increased RAM use, significant APK size increase, slower execution, and quickly hitting the DEX +symbol limit.

        + +

        For more information, see the "Nano version" section in the protobuf readme.

        + + + +

        Avoid dependency injection frameworks

        + +

        Using a dependency injection framework such as Guice or +RoboGuice may be +attractive because they can simplify the code you write and provide an adaptive environment +that's useful for testing and other configuration changes. However, these frameworks tend to perform +a lot of process initialization by scanning your code for annotations, which can require significant +amounts of your code to be mapped into RAM even though you don't need it. These mapped pages are +allocated into clean memory so Android can drop them, but that won't happen until the pages have +been left in memory for a long period of time.

        + + +

        Be careful about using external libraries

        + +

        External library code is often not written for mobile environments and can be inefficient when used +for work on a mobile client. At the very least, when you decide to use an external library, you +should assume you are taking on a significant porting and maintenance burden to optimize the +library for mobile. Plan for that work up-front and analyze the library in terms of code size and +RAM footprint before deciding to use it at all.

        + +

        Even libraries supposedly designed for use on Android are potentially dangerous because each +library may do things differently. For example, one library may use nano protobufs while another +uses micro protobufs. Now you have two different protobuf implementations in your app. This can and +will also happen with different implementations of logging, analytics, image loading frameworks, +caching, and all kinds of other things you don't expect. ProGuard won't save you here because these +will all be lower-level dependencies that are required by the features for which you want the +library. This becomes especially problematic when you use an {@link android.app.Activity} +subclass from a library (which +will tend to have wide swaths of dependencies), when libraries use reflection (which is common and +means you need to spend a lot of time manually tweaking ProGuard to get it to work), and so on.

        + +

        Also be careful not to fall into the trap of using a shared library for one or two features out of +dozens of other things it does; you don't want to pull in a large amount of code and overhead that +you don't even use. At the end of the day, if there isn't an existing implementation that is a +strong match for what you need to do, it may be best if you create your own implementation.

        + + +

        Optimize overall performance

        + +

        A variety of information about optimizing your app's overall performance is available +in other documents listed in Best Practices +for Performance. Many of these documents include optimizations tips for CPU performance, but +many of these tips also help optimize your app's memory use, such as by reducing the number of +layout objects required by your UI.

        + +

        You should also read about optimizing +your UI with the layout debugging tools and take advantage of +the optimization suggestions provided by the lint tool.

        + + +

        Use ProGuard to strip out any unneeded code

        + +

        The ProGuard tool shrinks, +optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and +methods with semantically obscure names. Using ProGuard can make your code more compact, requiring +fewer RAM pages to be mapped.

        + + +

        Use zipalign on your final APK

        + +

        If you do any post-processing of an APK generated by a build system (including signing it +with your final production certificate), then you must run zipalign on it to have it re-aligned. +Failing to do so can cause your app to require significantly more RAM, because things like +resources can no longer be mmapped from the APK.

        + +

        Note: Google Play Store does not accept APK files that +are not zipaligned.

        + + +

        Analyze your RAM usage

        + +

        Once you achieve a relatively stable build, begin analyzing how much RAM your app is using +throughout all stages of its lifecycle. For information about how to analyze your app, read Investigating Your RAM Usage.

        + + + + +

        Use multiple processes

        + +

        If it's appropriate for your app, an advanced technique that may help you manage your app's +memory is dividing components of your app into multiple processes. This technique must always be +used carefully and most apps should not run multiple processes, as it can easily +increase—rather than decrease—your RAM footprint if done incorrectly. It is primarily +useful to apps that may run significant work in the background as well as the foreground and can +manage those operations separately.

        + + +

        An example of when multiple processes may be appropriate is when building a music player that +plays music from a service for long period of time. If +the entire app runs in one process, then many of the allocations performed for its activity UI must +be kept around as long as it is playing music, even if the user is currently in another app and the +service is controlling the playback. An app like this may be split into two process: one for its +UI, and the other for the work that continues running in the background service.

        + +

        You can specify a separate process for each app component by declaring the {@code android:process} attribute +for each component in the manifest file. For example, you can specify that your service should run +in a process separate from your app's main process by declaring a new process named "background" +(but you can name the process anything you like):

        + +
        +<service android:name=".PlaybackService"
        +         android:process=":background" />
        +
        + +

        Your process name should begin with a colon (':') to ensure that the process remains private to +your app.

        + +

        Before you decide to create a new process, you need to understand the memory implications. +To illustrate the consequences of each process, consider that an empty process doing basically +nothing has an extra memory footprint of about 1.4MB, as shown by the memory information +dump below.

        + +
        +adb shell dumpsys meminfo com.example.android.apis:empty
        +
        +** MEMINFO in pid 10172 [com.example.android.apis:empty] **
        +                Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
        +              Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
        +             ------  ------  ------  ------  ------  ------  ------  ------  ------
        +  Native Heap     0       0       0       0       0       0    1864    1800      63
        +  Dalvik Heap   764       0    5228     316       0       0    5584    5499      85
        + Dalvik Other   619       0    3784     448       0       0
        +        Stack    28       0       8      28       0       0
        +    Other dev     4       0      12       0       0       4
        +     .so mmap   287       0    2840     212     972       0
        +    .apk mmap    54       0       0       0     136       0
        +    .dex mmap   250     148       0       0    3704     148
        +   Other mmap     8       0       8       8      20       0
        +      Unknown   403       0     600     380       0       0
        +        TOTAL  2417     148   12480    1392    4832     152    7448    7299     148
        +
        + +

        Note: More information about how to read this output is provided +in Investigating +Your RAM Usage. The key data here is the Private Dirty and Private +Clean memory, which shows that this process is using almost 1.4MB of non-pageable RAM +(distributed across the Dalvik heap, native allocations, book-keeping, and library-loading), +and another 150K of RAM for code that has been mapped in to execute.

        + +

        This memory footprint for an empty process is fairly significant and it can quickly +grow as you start doing work in that process. For +example, here is the memory use of a process that is created only to show an activity with some +text in it:

        + +
        +** MEMINFO in pid 10226 [com.example.android.helloactivity] **
        +                Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
        +              Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
        +             ------  ------  ------  ------  ------  ------  ------  ------  ------
        +  Native Heap     0       0       0       0       0       0    3000    2951      48
        +  Dalvik Heap  1074       0    4928     776       0       0    5744    5658      86
        + Dalvik Other   802       0    3612     664       0       0
        +        Stack    28       0       8      28       0       0
        +       Ashmem     6       0      16       0       0       0
        +    Other dev   108       0      24     104       0       4
        +     .so mmap  2166       0    2824    1828    3756       0
        +    .apk mmap    48       0       0       0     632       0
        +    .ttf mmap     3       0       0       0      24       0
        +    .dex mmap   292       4       0       0    5672       4
        +   Other mmap    10       0       8       8      68       0
        +      Unknown   632       0     412     624       0       0
        +        TOTAL  5169       4   11832    4032   10152       8    8744    8609     134
        +
        + +

        The process has now almost tripled in size, to 4MB, simply by showing some text in the UI. This +leads to an important conclusion: If you are going to split your app into multiple processes, only +one process should be responsible for UI. Other processes should avoid any UI, as this will quickly +increase the RAM required by the process (especially once you start loading bitmap assets and other +resources). It may then be hard or impossible to reduce the memory usage once the UI is drawn.

        + +

        Additionally, when running more than one process, it's more important than ever that you keep your +code as lean as possible, because any unnecessary RAM overhead for common implementations are now +replicated in each process. For example, if you are using enums (though you should not use enums), all of +the RAM needed to create and initialize those constants is duplicated in each process, and any +abstractions you have with adapters and temporaries or other overhead will likewise be replicated.

        + +

        Another concern with multiple processes is the dependencies that exist between them. For example, +if your app has a content provider that you have running in the default process which also hosts +your UI, then code in a background process that uses that content provider will also require that +your UI process remain in RAM. If your goal is to have a background process that can run +independently of a heavy-weight UI process, it can't have dependencies on content providers or +services that execute in the UI process.

        + + + + + + + + + + + diff --git a/docs/html/training/articles/perf-anr.jd b/docs/html/training/articles/perf-anr.jd index d3b2318d56f072ea53544d7105a3128fe604018f..87cfc1c985bda80eaf0008117fb12de7b5505477 100644 --- a/docs/html/training/articles/perf-anr.jd +++ b/docs/html/training/articles/perf-anr.jd @@ -8,7 +8,7 @@ page.article=true

        In this document

        -
          +
          1. What Triggers ANR?
          2. How to Avoid ANRs
          3. Reinforcing Responsiveness
          4. diff --git a/docs/html/training/articles/perf-jni.jd b/docs/html/training/articles/perf-jni.jd index 0d1f04e2e7b0f0952c20b95ba8c9fb6bfb988997..9f880ec557a08fa3310d6619aba649de6e02a64e 100644 --- a/docs/html/training/articles/perf-jni.jd +++ b/docs/html/training/articles/perf-jni.jd @@ -8,7 +8,7 @@ page.article=true

            In this document

            -
              +
              1. JavaVM and JNIEnv
              2. Threads
              3. jclass, jmethodID, and jfieldID
              4. diff --git a/docs/html/training/articles/perf-tips.jd b/docs/html/training/articles/perf-tips.jd index ab1632a2345b48addb56a8c3441d6dd2a070d555..1660b7f4ced28ca0b8f151185269eea2acb2a5a5 100644 --- a/docs/html/training/articles/perf-tips.jd +++ b/docs/html/training/articles/perf-tips.jd @@ -6,7 +6,7 @@ page.article=true

                In this document

                -
                  +
                  1. Avoid Creating Unnecessary Objects
                  2. Prefer Static Over Virtual
                  3. Use Static Final For Constants
                  4. diff --git a/docs/html/training/articles/security-ssl.jd b/docs/html/training/articles/security-ssl.jd index d3f68e2713ca3e05976ff99cce08d3b1376e04ae..f52865acff9e34d3e18e1bad44773445e272c0af 100644 --- a/docs/html/training/articles/security-ssl.jd +++ b/docs/html/training/articles/security-ssl.jd @@ -250,7 +250,7 @@ due to a self-signed certificate, which means the server is behaving as its own This is similar to an unknown certificate authority, so you can use the same approach from the previous section.

                    -

                    You can create yout own {@link javax.net.ssl.TrustManager}, +

                    You can create your own {@link javax.net.ssl.TrustManager}, this time trusting the server certificate directly. This has all of the downsides discussed earlier of tying your app directly to a certificate, but can be done securely. However, you should be careful to make sure your self-signed certificate has a diff --git a/docs/html/training/articles/security-tips.jd b/docs/html/training/articles/security-tips.jd index 1ac56b9a77c10a2ebb88edc3ee38ef935d40d9f2..54aebac72502fa07e9a04b47c00734a5e08937c3 100644 --- a/docs/html/training/articles/security-tips.jd +++ b/docs/html/training/articles/security-tips.jd @@ -553,7 +553,7 @@ android.content.Context#sendOrderedBroadcast sendOrderedBroadcast()}, or an explicit intent to a specific application component.

                    Note that ordered broadcasts can be “consumed†by a recipient, so they -may not be delivered to all applications. If you are sending an intent that muse be delivered +may not be delivered to all applications. If you are sending an intent that must be delivered to a specific receiver, then you must use an explicit intent that declares the receiver by nameintent.

                    diff --git a/docs/html/training/articles/smp.jd b/docs/html/training/articles/smp.jd index 0f667d7202ce3236ffc86fa0aa412242301d0837..7240eecbe01e09464a2e0161d43c1c08bc9f990c 100644 --- a/docs/html/training/articles/smp.jd +++ b/docs/html/training/articles/smp.jd @@ -1057,7 +1057,7 @@ an “impossible†state.

                    fix them. Before we do that, we need to discuss the use of a basic language feature.

                    -

                    C/C+++ and "volatile"

                    +

                    C/C++ and "volatile"

                    When writing single-threaded code, declaring a variable “volatile†can be very useful. The compiler will not omit or reorder accesses to volatile diff --git a/docs/html/training/basics/actionbar/adding-buttons.jd b/docs/html/training/basics/actionbar/adding-buttons.jd index 5fb0d59bff282ce8cc968ad9b03ae841b3066c6c..26c9d0ea69443386576444e1052cee47e9b36c1e 100644 --- a/docs/html/training/basics/actionbar/adding-buttons.jd +++ b/docs/html/training/basics/actionbar/adding-buttons.jd @@ -74,7 +74,21 @@ is available in the action bar, but the Settings action should always appear in the overflow. (By default, all actions appear in the overflow, but it's good practice to explicitly declare your design intentions for each action.) -

                    However, if your app is using the Support Library for compatibility on versions +

                    The {@code icon} attribute requires a resource ID for an +image. The name that follows {@code @drawable/} must be the name of a bitmap image you've +saved in your project's {@code res/drawable/} directory. For example, +{@code "@drawable/ic_action_search"} refers to {@code ic_action_search.png}. +Likewise, the {@code title} attribute uses a string resource that's defined by an XML +file in your project's {@code res/values/} directory, as discussed in Building a Simple User +Interface. + +

                    Note: When creating icons and other bitmap images for your app, +it's important that you provide multiple versions that are each optimized for a different screen +density. This is discussed more in the lesson about Supporting Different Screens. + +

                    If your app is using the Support Library for compatibility on versions as low as Android 2.1, the {@code showAsAction} attribute is not available from the {@code android:} namespace. Instead this attribute is provided by the Support Library and you must define your own XML namespace and use that namespace as the attribute prefix. diff --git a/docs/html/training/basics/actionbar/styling.jd b/docs/html/training/basics/actionbar/styling.jd index a1cc10cd5e73abaac9b1786fca561be180ac29f5..1f76e03f31e196d04feb47b1ecd98007fed4a080 100644 --- a/docs/html/training/basics/actionbar/styling.jd +++ b/docs/html/training/basics/actionbar/styling.jd @@ -20,7 +20,7 @@ trainingnavtop=true

                    @@ -146,13 +146,13 @@ background like this:

                    <style name="CustomActionBarTheme" parent="@style/Theme.Holo.Light.DarkActionBar"> <item name="android:actionBarStyle">@style/MyActionBar</item> - <style> + </style> <!-- ActionBar styles --> <style name="MyActionBar" parent="@style/Widget.Holo.Light.ActionBar.Solid.Inverse"> <item name="android:background">@drawable/actionbar_background</item> - <style> + </style> </resources> @@ -178,7 +178,7 @@ background like this:

                    <!-- Support library compatibility --> <item name="actionBarStyle">@style/MyActionBar</item> - <style> + </style> <!-- ActionBar styles --> <style name="MyActionBar" @@ -187,7 +187,7 @@ background like this:

                    <!-- Support library compatibility --> <item name="background">@drawable/actionbar_background</item> - <style> + </style> </resources> @@ -236,25 +236,25 @@ for each text element:

                    <item name="android:actionBarStyle">@style/MyActionBar</item> <item name="android:actionBarTabTextStyle">@style/MyActionBarTabText</item> <item name="android:actionMenuTextColor">@color/actionbar_text</item> - <style> + </style> <!-- ActionBar styles --> <style name="MyActionBar" parent="@style/Widget.Holo.ActionBar"> <item name="android:titleTextStyle">@style/MyActionBarTitleText</item> - <style> + </style> <!-- ActionBar title text --> <style name="MyActionBarTitleText" parent="@style/TextAppearance.Holo.Widget.ActionBar.Title"> <item name="android:textColor">@color/actionbar_text</item> - <style> + </style> <!-- ActionBar tabs text styles --> <style name="MyActionBarTabText" parent="@style/Widget.Holo.ActionBar.TabText"> <item name="android:textColor">@color/actionbar_text</item> - <style> + </style> </resources> @@ -280,7 +280,7 @@ for each text element:

                    <item name="actionBarStyle">@style/MyActionBar</item> <item name="actionBarTabTextStyle">@style/MyActionBarTabText</item> <item name="actionMenuTextColor">@color/actionbar_text</item> - <style> + </style> <!-- ActionBar styles --> <style name="MyActionBar" @@ -289,21 +289,21 @@ for each text element:

                    <!-- Support library compatibility --> <item name="titleTextStyle">@style/MyActionBarTitleText</item> - <style> + </style> <!-- ActionBar title text --> <style name="MyActionBarTitleText" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"> <item name="android:textColor">@color/actionbar_text</item> <!-- The textColor property is backward compatible with the Support Library --> - <style> + </style> <!-- ActionBar tabs text --> <style name="MyActionBarTabText" parent="@style/Widget.AppCompat.ActionBar.TabText"> <item name="android:textColor">@color/actionbar_text</item> <!-- The textColor property is backward compatible with the Support Library --> - <style> + </style> </resources> @@ -392,14 +392,14 @@ for several different states of an action bar tab:

                    <style name="CustomActionBarTheme" parent="@style/Theme.Holo"> <item name="android:actionBarTabStyle">@style/MyActionBarTabs</item> - <style> + </style> <!-- ActionBar tabs styles --> <style name="MyActionBarTabs" parent="@style/Widget.Holo.ActionBar.TabView"> <!-- tab indicator --> <item name="android:background">@drawable/actionbar_tab_indicator</item> - <style> + </style> </resources> @@ -420,7 +420,7 @@ for several different states of an action bar tab:

                    <!-- Support library compatibility --> <item name="actionBarTabStyle">@style/MyActionBarTabs</item> - <style> + </style> <!-- ActionBar tabs styles --> <style name="MyActionBarTabs" @@ -430,7 +430,7 @@ for several different states of an action bar tab:

                    <!-- Support library compatibility --> <item name="background">@drawable/actionbar_tab_indicator</item> - <style> + </style> </resources> @@ -442,7 +442,7 @@ for several different states of an action bar tab:

                    href="{@docRoot}guide/topics/ui/themes.html">Styles and Themes guide.
                  5. For even more complete styling for the action bar, try the Android Action Bar Style + href="http://www.actionbarstylegenerator.com">Android Action Bar Style Generator.
                \ No newline at end of file diff --git a/docs/html/training/basics/activity-lifecycle/starting.jd b/docs/html/training/basics/activity-lifecycle/starting.jd index dce6e30c69e060bb7be1a9e8e7fa06e60eec3cf4..90465991390ba7037978416004cff28a09dc1ab1 100644 --- a/docs/html/training/basics/activity-lifecycle/starting.jd +++ b/docs/html/training/basics/activity-lifecycle/starting.jd @@ -220,7 +220,7 @@ public void onCreate(Bundle savedInstanceState) {

                Caution: Using the {@link android.os.Build.VERSION#SDK_INT} to -prevent older system's from executing new APIs works in this way on Android 2.0 (API level +prevent older systems from executing new APIs works in this way on Android 2.0 (API level 5) and higher only. Older versions will encounter a runtime exception.

                Once the {@link android.app.Activity#onCreate onCreate()} finishes execution, the system diff --git a/docs/html/training/basics/firstapp/starting-activity.jd b/docs/html/training/basics/firstapp/starting-activity.jd index 6f7fa5d5777d5ec10aeea7170f0a76cdcee7aead..712eabc424c8a1d8841702fee39a05d0a045378a 100644 --- a/docs/html/training/basics/firstapp/starting-activity.jd +++ b/docs/html/training/basics/firstapp/starting-activity.jd @@ -426,10 +426,7 @@ on Android 4.0.

                That's it, you've built your first Android app!

                -

                To learn more about building Android apps, continue to follow the -basic training classes. The next class is Managing the Activity -Lifecycle.

                +

                To learn more, follow the link below to the next class.

                diff --git a/docs/html/training/basics/fragments/fragment-ui.jd b/docs/html/training/basics/fragments/fragment-ui.jd index db3119b252c2c822b94d32d229cb3b39994d8b25..14469bf0380fc48ee3f776f5028bced2fb59f671 100644 --- a/docs/html/training/basics/fragments/fragment-ui.jd +++ b/docs/html/training/basics/fragments/fragment-ui.jd @@ -122,11 +122,11 @@ public class MainActivity extends FragmentActivity { return; } - // Create an instance of ExampleFragment + // Create a new Fragment to be placed in the activity layout HeadlinesFragment firstFragment = new HeadlinesFragment(); - // In case this activity was started with special instructions from an Intent, - // pass the Intent's extras to the fragment as arguments + // In case this activity was started with special instructions from an + // Intent, pass the Intent's extras to the fragment as arguments firstFragment.setArguments(getIntent().getExtras()); // Add the fragment to the 'fragment_container' FrameLayout diff --git a/docs/html/training/basics/intents/filters.jd b/docs/html/training/basics/intents/filters.jd index 0090c985fadf4d7b06dc99b9a3e10a8d853a902d..9b6a1114b3bd5f46d8e1af95a0757f8a60f4028d 100644 --- a/docs/html/training/basics/intents/filters.jd +++ b/docs/html/training/basics/intents/filters.jd @@ -20,7 +20,8 @@ previous.link=result.html

                You should also read

        @@ -152,7 +153,7 @@ implicit intents will resolve to your activity.

        For more information about sending and receiving {@link android.content.Intent#ACTION_SEND} intents that perform social sharing behaviors, see the lesson about Receiving Content from Other Apps.

        +href="{@docRoot}training/sharing/receive.html">Receiving Simple Data from Other Apps.

        Handle the Intent in Your Activity

        diff --git a/docs/html/training/basics/intents/index.jd b/docs/html/training/basics/intents/index.jd index 8876a331914f800f1b38c0f207e4910e619edc50..59ba11f75b1de76579e414117d50288bc6874989 100644 --- a/docs/html/training/basics/intents/index.jd +++ b/docs/html/training/basics/intents/index.jd @@ -19,7 +19,8 @@ Lifecycle)

        You should also read

        diff --git a/docs/html/training/basics/intents/result.jd b/docs/html/training/basics/intents/result.jd index 24ecc46f5819849644b4b0ceddd160151a2a1b04..64fbb8b4071085d1d82a21e8bff1cd8c85a0da6a 100644 --- a/docs/html/training/basics/intents/result.jd +++ b/docs/html/training/basics/intents/result.jd @@ -21,7 +21,8 @@ next.link=filters.html

        You should also read

        @@ -71,7 +72,7 @@ private void pickContact() {

        Receive the Result

        -

        When the user is done with the subsequent activity and returns, the system calls your activity's +

        When the user is done with the subsequent activity and returns, the system calls your activity's {@link android.app.Activity#onActivityResult onActivityResult()} method. This method includes three arguments:

        diff --git a/docs/html/training/basics/intents/sending.jd b/docs/html/training/basics/intents/sending.jd index aba3896825b19dd69f1a7c2eca6495aebf1d2516..79c017b3ae9f2d850d2efbb539fb1657fbeac4b3 100644 --- a/docs/html/training/basics/intents/sending.jd +++ b/docs/html/training/basics/intents/sending.jd @@ -22,7 +22,7 @@ next.link=result.html

        You should also read

        @@ -200,7 +200,7 @@ Intent mapIntent = new Intent(Intent.ACTION_VIEW, location); PackageManager packageManager = {@link android.content.Context#getPackageManager()}; List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0); boolean isIntentSafe = activities.size() > 0; - + // Start an activity if it's safe if (isIntentSafe) { startActivity(mapIntent); diff --git a/docs/html/training/basics/supporting-devices/screens.jd b/docs/html/training/basics/supporting-devices/screens.jd index 1114f211b420b3bd8a019936bd3bc717429fe94d..e52ee701fb5e1b2cd7b9b770df99e74b0f1419fc 100644 --- a/docs/html/training/basics/supporting-devices/screens.jd +++ b/docs/html/training/basics/supporting-devices/screens.jd @@ -23,8 +23,8 @@ next.link=platforms.html diff --git a/docs/html/training/beam-files/index.jd b/docs/html/training/beam-files/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..e4bac2e4577b23cef7288a8bfb378a742d2a05d8 --- /dev/null +++ b/docs/html/training/beam-files/index.jd @@ -0,0 +1,62 @@ +page.title=Sharing Files with NFC +page.tags="NfcAdapter","Android Beam","share","file transfer" + +trainingnavtop=true +startpage=true + + +@jd:body + +
        +
        + +

        Dependencies and prerequisites

        +
          +
        • Android 4.1 (API Level 16) or higher
        • +
        • At least two NFC-enabled Android devices (NFC is not supported in the emulator)
        • +
        + +

        You should also read

        + + +
        +
        + +

        + Android allows you to transfer large files between devices using the Android Beam file transfer + feature. This feature has a simple API and allows users to start the transfer process by simply + touching devices. In response, Android Beam file transfer automatically copies files from one + device to the other and notifies the user when it's finished. +

        +

        + While the Android Beam file transfer API handles large amounts of data, the Android Beam NDEF + transfer API introduced in Android 4.0 (API level 14) handles small amounts of data such as + URIs or other small messages. In addition, Android Beam is only one of the features available + in the Android NFC framework, which allows you to read NDEF messages from NFC tags. To learn + more about Android Beam, see the topic + Beaming NDEF Messages to Other Devices. To learn more about the NFC framework, see the + Near Field Communication API guide. +

        +

        Lessons

        +
        +
        + Sending Files to Another Device +
        +
        Learn how to set up your app to send files to another device.
        + +
        + Receiving Files from Another Device +
        +
        + Learn how to set up your app to receive files sent by another device. +
        +
        + + diff --git a/docs/html/training/beam-files/receive-files.jd b/docs/html/training/beam-files/receive-files.jd new file mode 100644 index 0000000000000000000000000000000000000000..06136125e8a61e4d9ddc0d335c6fcc031034b083 --- /dev/null +++ b/docs/html/training/beam-files/receive-files.jd @@ -0,0 +1,313 @@ +page.title=Receiving Files from Another Device + +trainingnavtop=true +@jd:body + + + +

        + Android Beam file transfer copies files to a special directory on the receiving device. It also + scans the copied files using the Android Media Scanner and adds entries for media files to + the {@link android.provider.MediaStore} provider. This lesson shows you how to respond when the + file copy is complete, and how to locate the copied files on the receiving device. +

        +

        Respond to a Request to Display Data

        +

        + When Android Beam file transfer finishes copying files to the receiving device, it posts a + notification containing an {@link android.content.Intent} with the action + {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}, the MIME type of the first file that + was transferred, and a URI that points to the first file. When the user clicks the notification, + this intent is sent out to the system. To have your app respond to this intent, add an + <intent-filter> element for the + <activity> element of the {@link android.app.Activity} that should respond. + In the <intent-filter> element, add the following child elements: +

        +
        +
        + <action android:name="android.intent.action.VIEW" /> +
        +
        + Matches the {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent sent from the + notification. +
        +
        + <category android:name="android.intent.category.CATEGORY_DEFAULT" /> +
        +
        + Matches an {@link android.content.Intent} that doesn't have an explicit category. +
        +
        + <data android:mimeType="mime-type" /> +
        +
        + Matches a MIME type. Specify only those MIME types that your app can handle. +
        +
        +

        + For example, the following snippet shows you how to add an intent filter that + triggers the activity com.example.android.nfctransfer.ViewActivity: +

        +
        +    <activity
        +        android:name="com.example.android.nfctransfer.ViewActivity"
        +        android:label="Android Beam Viewer" >
        +        ...
        +        <intent-filter>
        +            <action android:name="android.intent.action.VIEW"/>
        +            <category android:name="android.intent.category.DEFAULT"/>
        +            ...
        +        </intent-filter>
        +    </activity>
        +
        +

        + Note: Android Beam file transfer is not the only source of an + {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent. Other apps on the receiving + device can also send an {@link android.content.Intent} with this action. + Handling this situation is discussed in the section Get the directory from a content URI. +

        +

        Request File Permissions

        +

        + To read files that Android Beam file transfer copies to the device, request the permission + {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}. For example: +

        +
        +    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        +

        + If you want to copy transferred files to your app's own storage area, request the permission + {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} instead. + {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} includes + {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}. +

        +

        + Note: As of Android 4.2.2 (API level 17), the permission + {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE} is + only enforced if the user chooses to do so. Future versions of the platform may require this + permission in all cases. To ensure forward compatibility, request the permission now, before it + becomes required. +

        +

        + Since your app has control over its internal storage area, you don't need to request + write permission to copy a transferred file to your internal storage area. +

        +

        Get the Directory for Copied Files

        +

        + Android Beam file transfer copies all the files in a single transfer to one directory + on the receiving device. The URI in the content {@link android.content.Intent} sent by the + Android Beam file transfer notification points to the first transferred file. However, your + app may also receive an {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent from a + source other than Android Beam file transfer. To determine how you should handle the incoming + {@link android.content.Intent}, you need to examine its scheme and authority. +

        +

        + To get the scheme for the URI, call {@link android.net.Uri#getScheme() Uri.getScheme()}. The + following code snippet shows you how to determine the scheme and handle the URI accordingly: +

        +
        +public class MainActivity extends Activity {
        +    ...
        +    // A File object containing the path to the transferred files
        +    private File mParentPath;
        +    // Incoming Intent
        +    private Intent mIntent;
        +    ...
        +    /*
        +     * Called from onNewIntent() for a SINGLE_TOP Activity
        +     * or onCreate() for a new Activity. For onNewIntent(),
        +     * remember to call setIntent() to store the most
        +     * current Intent
        +     *
        +     */
        +    private void handleViewIntent() {
        +        ...
        +        // Get the Intent action
        +        mIntent = getIntent();
        +        String action = mIntent.getAction();
        +        /*
        +         * For ACTION_VIEW, the Activity is being asked to display data.
        +         * Get the URI.
        +         */
        +        if (TextUtils.equals(action, Intent.ACTION_VIEW)) {
        +            // Get the URI from the Intent
        +            Uri beamUri = mIntent.getData();
        +            /*
        +             * Test for the type of URI, by getting its scheme value
        +             */
        +            if (TextUtils.equals(beamUri.getScheme(), "file")) {
        +                mParentPath = handleFileUri(beamUri);
        +            } else if (TextUtils.equals(
        +                    beamUri.getScheme(), "content")) {
        +                mParentPath = handleContentUri(beamUri);
        +            }
        +        }
        +        ...
        +    }
        +    ...
        +}
        +
        +

        Get the directory from a file URI

        +

        + If the incoming {@link android.content.Intent} contains a file URI, the URI contains the + absolute file name of a file, including the full directory path and file name. For Android Beam + file transfer, the directory path points to the location of the other transferred files, if + any. To get the directory path, get the path part of the URI, which contains all of the URI + except the file: prefix. Create a {@link java.io.File} from the path part, then + get the parent path of the {@link java.io.File}: +

        +
        +    ...
        +    public String handleFileUri(Uri beamUri) {
        +        // Get the path part of the URI
        +        String fileName = beamUri.getPath();
        +        // Create a File object for this filename
        +        File copiedFile = new File(fileName);
        +        // Get a string containing the file's parent directory
        +        return copiedFile.getParent();
        +    }
        +    ...
        +
        + +

        Get the directory from a content URI

        +

        + If the incoming {@link android.content.Intent} contains a content URI, the URI may point to a + directory and file name stored in the {@link android.provider.MediaStore} content provider. You + can detect a content URI for {@link android.provider.MediaStore} by testing the URI's + authority value. A content URI for {@link android.provider.MediaStore} may come from + Android Beam file transfer or from another app, but in both cases you can retrieve a directory + and file name for the content URI. +

        +

        + You can also receive an incoming {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} + intent containing a content URI for a content provider other than + {@link android.provider.MediaStore}. In this case, the content URI doesn't contain the + {@link android.provider.MediaStore} authority value, and the content URI usually doesn't point + to a directory. +

        +

        + Note: For Android Beam file transfer, you receive a content URI in the + {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent if the first incoming file + has a MIME type of "audio/*", "image/*", or "video/*", indicating that the file is media- + related. Android Beam file transfer indexes the media files it transfers by running Media + Scanner on the directory where it stores transferred files. Media Scanner writes its results + to the {@link android.provider.MediaStore} content provider, then it passes a content URI + for the first file back to Android Beam file transfer. This content URI is the one you + receive in the notification {@link android.content.Intent}. To get the directory + of the first file, you retrieve it from {@link android.provider.MediaStore} using the content + URI. +

        +

        Determine the content provider

        +

        + To determine if you can retrieve a file directory from the content URI, determine the + the content provider associated with the URI by calling + {@link android.net.Uri#getAuthority Uri.getAuthority()} to get the URI's authority. The + result has two possible values: +

        +
        +
        + {@link android.provider.MediaStore#AUTHORITY MediaStore.AUTHORITY} +
        +
        + The URI is for a file or files tracked by {@link android.provider.MediaStore}. Retrieve the + full file name from {@link android.provider.MediaStore}, and get directory from the file + name. +
        +
        + Any other authority value +
        +
        + A content URI from another content provider. Display the data associated with the content + URI, but don't get the file directory. +
        +
        +

        + To get the directory for a {@link android.provider.MediaStore} content URI, + run a query that specifies the incoming content URI for the {@link android.net.Uri} argument and + the column {@link android.provider.MediaStore.MediaColumns#DATA MediaColumns.DATA} for the + projection. The returned {@link android.database.Cursor} contains the full path and name for + the file represented by the URI. This path also contains all the other files that Android Beam + file transfer just copied to the device. +

        +

        + The following snippet shows you how to test the authority of the content URI and retrieve the + the path and file name for the transferred file: +

        +
        +    ...
        +    public String handleContentUri(Uri beamUri) {
        +        // Position of the filename in the query Cursor
        +        int filenameIndex;
        +        // File object for the filename
        +        File copiedFile;
        +        // The filename stored in MediaStore
        +        String fileName;
        +        // Test the authority of the URI
        +        if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY)) {
        +            /*
        +             * Handle content URIs for other content providers
        +             */
        +        // For a MediaStore content URI
        +        } else {
        +            // Get the column that contains the file name
        +            String[] projection = { MediaStore.MediaColumns.DATA };
        +            Cursor pathCursor =
        +                    getContentResolver().query(beamUri, projection,
        +                    null, null, null);
        +            // Check for a valid cursor
        +            if (pathCursor != null &&
        +                    pathCursor.moveToFirst()) {
        +                // Get the column index in the Cursor
        +                filenameIndex = pathCursor.getColumnIndex(
        +                        MediaStore.MediaColumns.DATA);
        +                // Get the full file name including path
        +                fileName = pathCursor.getString(filenameIndex);
        +                // Create a File object for the filename
        +                copiedFile = new File(fileName);
        +                // Return the parent directory of the file
        +                return new File(copiedFile.getParent());
        +             } else {
        +                // The query didn't work; return null
        +                return null;
        +             }
        +        }
        +    }
        +    ...
        +
        +

        + To learn more about retrieving data from a content provider, see the section + Retrieving Data from the Provider. +

        diff --git a/docs/html/training/beam-files/send-files.jd b/docs/html/training/beam-files/send-files.jd new file mode 100644 index 0000000000000000000000000000000000000000..917b87f11594fc760a46dd2f2e7c7590fcde0f0b --- /dev/null +++ b/docs/html/training/beam-files/send-files.jd @@ -0,0 +1,294 @@ +page.title=Sending Files to Another Device + +trainingnavtop=true +@jd:body + + + +

        + This lesson shows you how to design your app to send large files to another device using + Android Beam file transfer. To send files, you request permission to use NFC and external + storage, test to ensure your device supports NFC, and provide URIs to Android Beam file + transfer. +

        +

        + The Android Beam file transfer feature has the following requirements: +

        +
          +
        1. + Android Beam file transfer for large files is only available in Android 4.1 (API level 16) + and higher. +
        2. +
        3. + Files you want to transfer must reside in external storage. To learn more about using + external storage, read Using the External Storage. +
        4. +
        5. + Each file you want to transfer must be world-readable. You can set this permission by + calling the method {@link java.io.File#setReadable File.setReadable(true,false)}. +
        6. +
        7. + You must provide a file URI for the files you want to transfer. Android Beam file transfer + is unable to handle content URIs generated by + {@link android.support.v4.content.FileProvider#getUriForFile FileProvider.getUriForFile}. +
        8. +
        + +

        Declare Features in the Manifest

        +

        + First, edit your app manifest to declare the permissions and features your app needs. +

        +

        Request Permissions

        +

        + To allow your app to use Android Beam file transfer to send files from external storage using + NFC, you must request the following permissions in your app manifest: +

        +
        +
        + {@link android.Manifest.permission#NFC NFC} +
        +
        + Allows your app to send data over NFC. To specify this permission, add the following element + as a child of the <manifest> element: +
        +    <uses-permission android:name="android.permission.NFC" />
        +
        +
        +
        + {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE} +
        +
        + Allows your app to read from external storage. To specify this permission, add the following + element as a child of the + <manifest> element: +
        +    <uses-permission
        +            android:name="android.permission.READ_EXTERNAL_STORAGE" />
        +
        +

        + Note: As of Android 4.2.2 (API level 17), this permission is not + enforced. Future versions of the platform may require it for apps that want to read from + external storage. To ensure forward compatibility, request the permission now, before it + becomes required. +

        +
        +
        +

        Specify the NFC feature

        +

        + Specify that your app uses NFC, by adding a + <uses-feature> element as a child + of the <manifest> element. Set the android:required attribute to + true to indicate that your app won't function unless NFC is present. +

        +

        + The following snippet shows you how to specify the + <uses-feature> element: +

        +
        +<uses-feature
        +    android:name="android.hardware.nfc"
        +    android:required="true" />
        +

        + Note that if your app only uses NFC as an option, but still functions if NFC isn't present, you + should set android:required to false, and test for NFC in code. +

        +

        Specify Android Beam file transfer

        +

        + Since Android Beam file transfer is only available in Android 4.1 (API level 16) and later, + if your app depends on Android Beam file transfer for a key part of its functionality you must + specify the <uses-sdk> element with the + android:minSdkVersion="16" attribute. Otherwise, you can set + android:minSdkVersion to another value as necessary, and test for the platform + version in code, as described in the following section. +

        +

        Test for Android Beam File Transfer Support

        +

        + To specify in your app manifest that NFC is optional, you use the following element: +

        +
        +<uses-feature android:name="android.hardware.nfc" android:required="false" />
        +

        + If you set the attribute + android:required="false", you must test for NFC support and Android Beam file + transfer support in code. +

        +

        + To test for Android Beam file transfer support in code, start by testing that the device + supports NFC by calling {@link android.content.pm.PackageManager#hasSystemFeature + PackageManager.hasSystemFeature()} with the argument + {@link android.content.pm.PackageManager#FEATURE_NFC FEATURE_NFC}. Next, check that the Android + version supports Android Beam file transfer by testing the value of + {@link android.os.Build.VERSION#SDK_INT}. If Android Beam file transfer is supported, get an + instance of the NFC controller, which allows you to communicate with the NFC hardware. + For example: +

        +
        +public class MainActivity extends Activity {
        +    ...
        +    NfcAdapter mNfcAdapter;
        +    // Flag to indicate that Android Beam is available
        +    boolean mAndroidBeamAvailable  = false;
        +    ...
        +    @Override
        +    protected void onCreate(Bundle savedInstanceState) {
        +        ...
        +        // NFC isn't available on the device
        +        if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
        +            /*
        +             * Disable NFC features here.
        +             * For example, disable menu items or buttons that activate
        +             * NFC-related features
        +             */
        +            ...
        +        // Android Beam file transfer isn't supported
        +        } else if (Build.VERSION.SDK_INT <
        +                Build.VERSION_CODES.JELLY_BEAN_MR1) {
        +            // If Android Beam isn't available, don't continue.
        +            mAndroidBeamAvailable = false;
        +            /*
        +             * Disable Android Beam file transfer features here.
        +             */
        +            ...
        +        // Android Beam file transfer is available, continue
        +        } else {
        +        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        +        ...
        +        }
        +    }
        +    ...
        +}
        + +

        + Create a Callback Method that Provides Files +

        +

        + Once you've verified that the device supports Android Beam file transfer, add a callback + method that the system invokes when Android Beam file transfer detects that the user wants + to send files to another NFC-enabled device. In this callback method, return an array of + {@link android.net.Uri} objects. Android Beam file transfer copies the files represented by + these URIs to the receiving device. +

        +

        + To add the callback method, implement the + {@link android.nfc.NfcAdapter.CreateBeamUrisCallback} interface and its method + {@link android.nfc.NfcAdapter.CreateBeamUrisCallback#createBeamUris createBeamUris()}. The + following snippet shows you how to do this: +

        +
        +public class MainActivity extends Activity {
        +    ...
        +    // List of URIs to provide to Android Beam
        +    private Uri[] mFileUris = new Uri[10];
        +    ...
        +    /**
        +     * Callback that Android Beam file transfer calls to get
        +     * files to share
        +     */
        +    private class FileUriCallback implements
        +            NfcAdapter.CreateBeamUrisCallback {
        +        public FileUriCallback() {
        +        }
        +        /**
        +         * Create content URIs as needed to share with another device
        +         */
        +        @Override
        +        public Uri[] createBeamUris(NfcEvent event) {
        +            return mFileUris;
        +        }
        +    }
        +    ...
        +}
        +
        +

        + Once you've implemented the interface, provide the callback to Android Beam file transfer by + calling {@link android.nfc.NfcAdapter#setBeamPushUrisCallback setBeamPushUrisCallback()}. The + following snippet shows you how to do this: +

        +
        +public class MainActivity extends Activity {
        +    ...
        +    // Instance that returns available files from this app
        +    private FileUriCallback mFileUriCallback;
        +    ...
        +    @Override
        +    protected void onCreate(Bundle savedInstanceState) {
        +        ...
        +        // Android Beam file transfer is available, continue
        +        ...
        +        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        +        /*
        +         * Instantiate a new FileUriCallback to handle requests for
        +         * URIs
        +         */
        +        mFileUriCallback = new FileUriCallback();
        +        // Set the dynamic callback for URI requests.
        +        mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback,this);
        +        ...
        +    }
        +    ...
        +}
        +
        +

        + Note: You can also provide the array of {@link android.net.Uri} objects + directly to the NFC framework through your app's {@link android.nfc.NfcAdapter} instance. Choose + this approach if you can define the URIs to transfer before the NFC touch event occurs. + To learn more about this approach, see {@link android.nfc.NfcAdapter#setBeamPushUris + NfcAdapter.setBeamPushUris()}. +

        +

        Specify the Files to Send

        +

        + To transfer one or more files to another NFC-enabled device, get a file URI (a URI with a + file scheme) for each file and then add the URI to an array of + {@link android.net.Uri} objects. To transfer a file, you must also have permanent read access + for the file. For example, the following snippet shows you how to get a file URI from a file + name and then add the URI to the array: +

        +
        +        /*
        +         * Create a list of URIs, get a File,
        +         * and set its permissions
        +         */
        +        private Uri[] mFileUris = new Uri[10];
        +        String transferFile = "transferimage.jpg";
        +        File extDir = getExternalFilesDir(null);
        +        File requestFile = new File(extDir, transferFile);
        +        requestFile.setReadable(true, false);
        +        // Get a URI for the File and add it to the list of URIs
        +        fileUri = Uri.fromFile(requestFile);
        +        if (fileUri != null) {
        +            mFileUris[0] = fileUri;
        +        } else {
        +            Log.e("My Activity", "No File URI available for file.");
        +        }
        +
        diff --git a/docs/html/training/best-background.jd b/docs/html/training/best-background.jd new file mode 100644 index 0000000000000000000000000000000000000000..917eabb4fd6241c3bcd92bd9e37622b6a9c736de --- /dev/null +++ b/docs/html/training/best-background.jd @@ -0,0 +1,8 @@ +page.title=Best Practices for Background Jobs +page.trainingcourse=true + +@jd:body + + +

        These classes show you how to run jobs in the background to boost your +application's performance and minimize its drain on the battery.

        diff --git a/docs/html/training/building-content-sharing.jd b/docs/html/training/building-content-sharing.jd new file mode 100644 index 0000000000000000000000000000000000000000..52298c375be1e8feba860ce6fdd69a745517e0ca --- /dev/null +++ b/docs/html/training/building-content-sharing.jd @@ -0,0 +1,8 @@ +page.title=Building Apps with Content Sharing +page.trainingcourse=true + +@jd:body + + + +

        These classes teach you how to create apps that share data between apps and devices.

        diff --git a/docs/html/training/connect-devices-wirelessly/index.jd b/docs/html/training/connect-devices-wirelessly/index.jd index f27b9c39d96353a555999ccd150de1e7abaef62b..db79abe6733fecbf1ac28f3de25e826bade504de 100644 --- a/docs/html/training/connect-devices-wirelessly/index.jd +++ b/docs/html/training/connect-devices-wirelessly/index.jd @@ -17,7 +17,7 @@ startpage=true

        You should also read

        @@ -37,8 +37,8 @@ other machines on the same network.

        This class describes the key APIs for finding and connecting to other devices from your application. Specifically, it describes the NSD API for discovering available services and the Wi-Fi -Direct™ API for doing peer-to-peer wireless connections. This class also -shows you how to use NSD and Wi-Fi Direct in +Peer-to-Peer (P2P) API for doing peer-to-peer wireless connections. This class also +shows you how to use NSD and Wi-Fi P2P in combination to detect the services offered by a device and connect to the device when neither device is connected to a network.

        @@ -49,13 +49,13 @@ device when neither device is connected to a network.
        Learn how to broadcast services offered by your own application, discover services offered on the local network, and use NSD to determine the connection details for the service you wish to connect to.
        -
        Connecting with Wi-Fi Direct
        +
        Creating P2P Connections with Wi-Fi
        Learn how to fetch a list of nearby peer devices, create an access point - for legacy devices, and connect to other devices capable of Wi-Fi Direct + for legacy devices, and connect to other devices capable of Wi-Fi P2P connections.
        -
        Using Wi-Fi Direct for Service +
        Using Wi-Fi P2P for Service Discovery
        Learn how to discover services published by nearby devices without being - on the same network, using Wi-Fi Direct.
        + on the same network, using Wi-Fi P2P. diff --git a/docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd b/docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd index 5e276ded549185714efce0244edda630b5bc50d6..5c1321e852ce31c7584d8e812de608288d7fc081 100644 --- a/docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd +++ b/docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd @@ -1,7 +1,4 @@ -page.title=Using Wi-Fi Direct for Service Discovery -parent.title=Connecting Devices Wirelessly -parent.link=index.html - +page.title=Using Wi-Fi P2P for Service Discovery trainingnavtop=true @jd:body @@ -26,23 +23,23 @@ trainingnavtop=true

        The first lesson in this class, Using Network Service Discovery, showed you how to discover services that are connected to a local network. However, using -Wi-Fi Direct&trad; Service Discovery allows you to discover the services of nearby devices directly, -without being connected to a network. You can also advertise the services +Wi-Fi Peer-to-Peer (P2P) Service Discovery allows you to discover the services of nearby devices +directly, without being connected to a network. You can also advertise the services running on your device. These capabilities help you communicate between apps, even when no local network or hotspot is available.

        While this set of APIs is similar in purpose to the Network Service Discovery APIs outlined in a previous lesson, implementing them in code is very different. This lesson shows you how to discover services available from other devices, -using Wi-Fi Direct™. The lesson assumes that you're already familiar with the -Wi-Fi Direct API.

        +using Wi-Fi P2P. The lesson assumes that you're already familiar with the +Wi-Fi P2P API.

        Set Up the Manifest

        -

        In order to use Wi-Fi Direct, add the {@link +

        In order to use Wi-Fi P2P, add the {@link android.Manifest.permission#CHANGE_WIFI_STATE}, {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and {@link android.Manifest.permission#INTERNET} -permissions to your manifest. Even though Wi-Fi Direct doesn't require an +permissions to your manifest. Even though Wi-Fi P2P doesn't require an Internet connection, it uses standard Java sockets, and using these in Android requires the requested permissions.

        @@ -244,7 +241,7 @@ provided by the method hints at the problem. Here are the possible error values and what they mean

        {@link android.net.wifi.p2p.WifiP2pManager#P2P_UNSUPPORTED}
        -
        Wi-Fi Direct isn't supported on the device running the app.
        +
        Wi-Fi P2P isn't supported on the device running the app.
        {@link android.net.wifi.p2p.WifiP2pManager#BUSY}
        The system is to busy to process the request.
        {@link android.net.wifi.p2p.WifiP2pManager#ERROR}
        diff --git a/docs/html/training/connect-devices-wirelessly/nsd.jd b/docs/html/training/connect-devices-wirelessly/nsd.jd index 30f5c49575bf5662f12e4d844fa9e85bade7ac93..e07e2afdc7a81183be4225487b4e04888f51d5b8 100644 --- a/docs/html/training/connect-devices-wirelessly/nsd.jd +++ b/docs/html/training/connect-devices-wirelessly/nsd.jd @@ -1,10 +1,6 @@ page.title=Using Network Service Discovery -parent.title=Connecting Devices Wirelessly -parent.link=index.html trainingnavtop=true -next.title=Connecting with Wi-Fi Direct -next.link=wifi-direct.html @jd:body diff --git a/docs/html/training/connect-devices-wirelessly/wifi-direct.jd b/docs/html/training/connect-devices-wirelessly/wifi-direct.jd index b8ed6641a700bdd00624aaa2d2ec174a5323b6c7..d67ed237b64619dd0b96de04ff780219e30af73e 100644 --- a/docs/html/training/connect-devices-wirelessly/wifi-direct.jd +++ b/docs/html/training/connect-devices-wirelessly/wifi-direct.jd @@ -1,12 +1,6 @@ -page.title=Connecting with Wi-Fi Direct -parent.title=Connecting Devices Wirelessly -parent.link=index.html +page.title=Creating P2P Connections with Wi-Fi trainingnavtop=true -previous.title=Using Network Service Discovery -previous.link=nsd.html -next.title=Service Discovery with Wi-Fi Direct -next.link=nsd-wifi-direct.html @jd:body @@ -21,25 +15,32 @@ next.link=nsd-wifi-direct.html
      • Fetch the List of Peers
      • Connect to a Peer
      • +

        You should also read

        + -

        The Wi-Fi Direct™ APIs allow applications to connect to nearby devices without -needing to connect to a network or hotspot. This allows your application to quickly +

        The Wi-Fi peer-to-peer (P2P) APIs allow applications to connect to nearby devices without +needing to connect to a network or hotspot (Android's Wi-Fi P2P framework complies with the +Wi-Fi Direct™ certification program). + Wi-Fi P2P allows your application to quickly find and interact with nearby devices, at a range beyond the capabilities of Bluetooth.

        -This lesson shows you how to find and connect to nearby devices using Wi-Fi Direct. +This lesson shows you how to find and connect to nearby devices using Wi-Fi P2P.

        Set Up Application Permissions

        -

        In order to use Wi-Fi Direct, add the {@link +

        In order to use Wi-Fi P2P, add the {@link android.Manifest.permission#CHANGE_WIFI_STATE}, {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and {@link android.Manifest.permission#INTERNET} -permissions to your manifest. Wi-Fi Direct doesn't require an internet connection, +permissions to your manifest. Wi-Fi P2P doesn't require an internet connection, but it does use standard Java sockets, which require the {@link android.Manifest.permission#INTERNET} permission. -So you need the following permissions to use Wi-Fi Direct.

        +So you need the following permissions to use Wi-Fi P2P.

         <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        @@ -59,13 +60,13 @@ So you need the following permissions to use Wi-Fi Direct.

        Set Up a Broadcast Receiver and Peer-to-Peer Manager

        -

        To use Wi-Fi Direct, you need to listen for broadcast intents that tell your +

        To use Wi-Fi P2P, you need to listen for broadcast intents that tell your application when certain events have occurred. In your application, instantiate an {@link android.content.IntentFilter} and set it to listen for the following:

        {@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_STATE_CHANGED_ACTION}
        -
        Indicates whether Wi-Fi Peer-To-Peer (P2P) is enabled
        +
        Indicates whether Wi-Fi P2P is enabled
        {@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_PEERS_CHANGED_ACTION}
        Indicates that the available peer list has changed.
        {@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_CONNECTION_CHANGED_ACTION}
        @@ -80,7 +81,7 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); - // Indicates a change in the Wi-Fi Peer-to-Peer status. + // Indicates a change in the Wi-Fi P2P status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); // Indicates a change in the list of available peers. @@ -101,7 +102,7 @@ android.net.wifi.p2p.WifiP2pManager}, and call its {@link android.net.wifi.p2p.WifiP2pManager#initialize(Context, Looper, WifiP2pManager.ChannelListener) initialize()} method. This method returns a {@link android.net.wifi.p2p.WifiP2pManager.Channel} object, which you'll use later to -connect your app to the Wi-Fi Direct Framework.

        +connect your app to the Wi-Fi P2P framework.

         @Override
        @@ -126,7 +127,7 @@ method, add a condition to handle each P2P state change listed above.

        public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { - // Determine if Wifi Direct mode is enabled or not, alert + // Determine if Wifi P2P mode is enabled or not, alert // the Activity. int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { @@ -177,7 +178,7 @@ The best place to do this is the {@link android.app.Activity#onResume()} and

        Initiate Peer Discovery

        -

        To start searching for nearby devices with Wi-Fi Direct, call {@link +

        To start searching for nearby devices with Wi-Fi P2P, call {@link android.net.wifi.p2p.WifiP2pManager#discoverPeers(WifiP2pManager.Channel, WifiP2pManager.ActionListener) discoverPeers()}. This method takes the following arguments:

        @@ -218,7 +219,7 @@ formed.

        Fetch the List of Peers

        Now write the code that fetches and processes the list of peers. First implement the {@link android.net.wifi.p2p.WifiP2pManager.PeerListListener} -interface, which provides information about the peers that Wi-Fi Direct has +interface, which provides information about the peers that Wi-Fi P2P has detected. The following code snippet illustrates this.

        diff --git a/docs/html/training/id-auth/authenticate.jd b/docs/html/training/id-auth/authenticate.jd
        index 3084beac35a9c576bf3d6de2d98191443ffc1f43..65dbc3987bd3ecb3f711448f3b8540f052d0c07f 100644
        --- a/docs/html/training/id-auth/authenticate.jd
        +++ b/docs/html/training/id-auth/authenticate.jd
        @@ -79,7 +79,7 @@ a valid auth token from the Android Account Manager"/>
         
         

        To get an auth token you first need to request the {@link android.Manifest.permission#ACCOUNT_MANAGER} -to yourmanifest file. To actually do anything useful with the +to your manifest file. To actually do anything useful with the token, you'll also need to add the {@link android.Manifest.permission#INTERNET} permission.

        diff --git a/docs/html/training/id-auth/identify.jd b/docs/html/training/id-auth/identify.jd index d4a6f7accc2870edb09676a96ffa02f0a55c66ca..2b31bdd8c039b21a1361782197357f35762addb9 100644 --- a/docs/html/training/id-auth/identify.jd +++ b/docs/html/training/id-auth/identify.jd @@ -13,7 +13,7 @@ next.link=authenticate.html

        This lesson teaches you to

          -
        1. Determine if AccountManager for You
        2. +
        3. Determine if AccountManager is for You
        4. Decide What Type of Account to Use
        5. Request GET_ACCOUNT permission
        6. Query AccountManager for a List of Accounts
        7. @@ -41,7 +41,7 @@ that the user has stored on their device.

          -

          Determine if AccountManager for You

          +

          Determine if AccountManager is for You

          Applications typically try to remember the user using one of three techniques:

            diff --git a/docs/html/training/implementing-navigation/nav-drawer.jd b/docs/html/training/implementing-navigation/nav-drawer.jd index 2b5e4f809d649ab08da87b7a073ef4202a1ac2bf..9a948109ceb4147f990d441b579966f63d8826c6 100644 --- a/docs/html/training/implementing-navigation/nav-drawer.jd +++ b/docs/html/training/implementing-navigation/nav-drawer.jd @@ -26,9 +26,9 @@ trainingnavtop=true
        -Download the nav drawer icons -

        Android_Navigation_Drawer_Icon_20130516.zip

        +Download the Action Bar Icon Pack +

        Android_Design_Icons_20130926.zip

        @@ -304,8 +304,8 @@ it with its constructor, which requires the following arguments:

      • The {@link android.app.Activity} hosting the drawer.
      • The {@link android.support.v4.widget.DrawerLayout}.
      • A drawable resource to use as the drawer indicator. -

        Download the standard navigation icons (available for both dark and light themes).

        +

        The standard navigation drawer icon is available in the Download the Action Bar Icon Pack.

      • A String resource to describe the "open drawer" action (for accessibility).
      • A String resource to describe the "close drawer" action (for accessibility). diff --git a/docs/html/training/location/activity-recognition.jd b/docs/html/training/location/activity-recognition.jd index 47ba5f8ab4909fee9b95a96346da0db987fe739a..d50064b00d6eb2af21b513c3bce549ed02559bfa 100644 --- a/docs/html/training/location/activity-recognition.jd +++ b/docs/html/training/location/activity-recognition.jd @@ -31,13 +31,13 @@ trainingnavtop=true

        - This lesson shows you how to request activity recognition updates from Location Services. Activity recognition tries to detect the user's current physical activity, such as walking, driving, or standing still. Requests for updates go through an activity recognition client, which, while different from the location client used by location or geofencing, follows a similar pattern. Based on the update interval you choose, Location Services sends out activity information containing one or more possible activities and the confidence level for - each one. + each one. This lesson shows you how to request activity recognition updates from Location + Services.

        Request Activity Recognition Updates

        diff --git a/docs/html/training/location/index.jd b/docs/html/training/location/index.jd index 5ebbb84f69e682da5ca581a7c39966504b6c9fb1..e03eac666554e65f7ac22125348af502aef42bc6 100644 --- a/docs/html/training/location/index.jd +++ b/docs/html/training/location/index.jd @@ -85,4 +85,12 @@ startpage=true Learn how to recognize the user's current activity, such as walking, bicycling, or driving a car, and how to use this information to modify your app's location strategy. +

        + Testing Using Mock Locations +
        +
        + Learn how to test a location-aware app by injecting mock locations into Location + Services. In mock mode, Location Services sends out mock locations that you inject instead + of sensor-based locations. +
      • diff --git a/docs/html/training/location/location-testing.jd b/docs/html/training/location/location-testing.jd new file mode 100644 index 0000000000000000000000000000000000000000..e36bac13621e3d469b87f1b4bcdf505acfd00833 --- /dev/null +++ b/docs/html/training/location/location-testing.jd @@ -0,0 +1,371 @@ +page.title=Testing Using Mock Locations + +trainingnavtop=true +@jd:body + +
        +
        + + +

        This lesson teaches you to

        +
          +
        1. Turn On Mock Mode
        2. +
        3. Send Mock Locations
        4. +
        5. Run the Mock Location Provider App
        6. +
        7. Testing Tips +
        + +

        You should also read

        + + +

        Example Test App

        + +
        + Download the sample +

        LocationProvider.zip

        +
        + +
        +
        +

        + To test a location-aware app that uses Location Services, you don't need to move your device + from place to place to generate location data. Instead, you can put Location Services into mock + mode. In this mode, you can send mock {@link android.location.Location} objects to + Location Services, which then sends them to location clients. In mock mode, Location Services + also uses mock {@link android.location.Location} objects to trigger geofences. +

        +

        + Using mock locations has several advantages: +

        +
          +
        • + Mock locations allow you to create specific mock data, instead of trying to approximate + data by moving an actual device. +
        • +
        • + Since mock locations come from Location Services, they test every part of your + location-handling code. In addition, since you can send the mock data from outside your + production app, you don't have to disable or remove test code before you publish. +
        • +
        • + Since you don't have to generate test locations by moving a device, you can test an app + using the emulator. +
        • +
        +

        + The best way to use mock locations is to send them from a separate mock location provider app. + This lesson includes a provider app that you can download and use to test your own software. + Modify the provider app as necessary to suit your own needs. Some ideas for providing test data + to the app are listed in the section Managing test data. +

        +

        + The remainder of this lesson shows you how to turn on mock mode and use a location client to + send mock locations to Location Services. +

        +

        + Note: Mock locations have no effect on the activity recognition algorithm used + by Location Services. To learn more about activity recognition, see the lesson + Recognizing the User's Current Activity. +

        + +

        Turn On Mock Mode

        +

        + To send mock locations to Location Services in mock mode, a test app must request the permission + {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}. In addition, you must enable mock + locations on the test device using the option Enable mock locations. To learn how to + enable mock locations on the device, see + Setting up a Device for Development. +

        +

        + To turn on mock mode in Location Services, start by connecting a location client to Location + Services, as described in the lesson + Retrieving the Current Location. + Next, call the method +LocationClient.setMockMode(true). + Once you call this method, Location Services turns off its internal location providers and only + sends out the mock locations you provide it. The following snippet shows you how to call +LocationClient.setMockMode(true): +

        +
        +    // Define a LocationClient object
        +    public LocationClient mLocationClient;
        +    ...
        +    // Connect to Location Services
        +    mLocationClient.connect();
        +    ...
        +    // When the location client is connected, set mock mode
        +    mLocationClinet.setMockMode(true);
        +
        +

        + Once you have connected the location client to Location Services, you must keep it connected + until you finish sending out mock locations. Once you call +LocationClient.disconnect(), + Location Services returns to using its internal location providers. To turn off mock mode while + the location client is connected, call +LocationClient.setMockMode(false). +

        +

        Send Mock Locations

        +

        + Once you have set mock mode, you can create mock {@link android.location.Location} objects and + send them to Location Services. In turn, Location Services sends these mock + {@link android.location.Location} objects to connected location clients. Location Services also + uses the mock {@link android.location.Location} objects to control geofence triggering. +

        +

        + To create a new mock {@link android.location.Location}, create a new + {@link android.location.Location} object using your test data. Always set the provider + value to {@code flp}, which is the code that Location Services puts into the + {@link android.location.Location} objects it sends out. The following snippet shows you how + to create a new mock {@link android.location.Location}: +

        +
        +    private static final String PROVIDER = "flp";
        +    private static final double LAT = 37.377166;
        +    private static final double LNG = -122.086966;
        +    private static final float ACCURACY = 3.0f;
        +    ...
        +    /*
        +     * From input arguments, create a single Location with provider set to
        +     * "flp"
        +     */
        +    public Location createLocation(double lat, double lng, float accuracy) {
        +        // Create a new Location
        +        Location newLocation = new Location(PROVIDER);
        +        newLocation.setLatitude(lat);
        +        newLocation.setLongitude(lng);
        +        newLocation.setAccuracy(accuracy);
        +        return newLocation;
        +    }
        +    ...
        +    // Example of creating a new Location from test data
        +    Location testLocation = createLocation(LAT, LNG, ACCURACY);
        +
        +

        + In mock mode, to send a mock location to Location Services call the method +LocationClient.setMockLocation(). + For example: +

        +
        +    mLocationClient.setMockLocation(testLocation);
        +
        +

        + Location Services sets this mock location as the current location, and this location is sent + out as the next location update. If this new mock location moves across a geofence boundary, + Location Services triggers the geofence. +

        + +

        Run the Mock Location Provider App

        +

        + This section contains a brief overview of the mock location provider sample app + (available for download above) and gives you directions for testing an app using the sample app. +

        +

        Overview

        +

        + The mock location provider app included with this lesson sends mock + {@link android.location.Location} objects to Location Services from a background thread running + in a started {@link android.app.Service}. By using a started service, the provider app is able + to keep running even if the app's main {@link android.app.Activity} is destroyed because of + a configuration change or other system event. By using a background thread, the service is able + to perform a long-running test without blocking the UI thread. +

        +

        + The {@link android.app.Activity} that starts when you run the provider app allows you to + send test parameters to the {@link android.app.Service} and control the type of test you want. + You have the following options: +

        +
        +
        + Pause before test +
        +
        + The number of seconds to wait before the provider app starts sending test data to Location + Services. This interval allows you to switch from the provider app to the app under test + before the testing actually starts. +
        +
        + Send interval +
        +
        + The number of seconds that the provider app waits before it sends another mock location to + Location Services. See the section Testing Tips to learn more + about setting the send interval. +
        +
        + Run once +
        +
        + Switch from normal mode to mock mode, run through the test data once, switch back to + normal mode, and then kill the {@link android.app.Service}. +
        +
        + Run continuously +
        +
        + Switch from normal mode to mock mode, then run through the test data indefinitely. The + background thread and the started {@link android.app.Service} continue to run, even if the + main {@link android.app.Activity} is destroyed. +
        +
        + Stop test +
        +
        + If a continuous test is in progress, stop it; otherwise, return a warning message. The + started {@link android.app.Service} switches from mock mode to normal mode and then + stops itself. This also stops the background thread. +
        +
        +

        + Besides the options, the provider app has two status displays: +

        +
        +
        + App status +
        +
        + Displays messages related to the lifecycle of the provider app. +
        +
        + Connection status +
        +
        + Displays messages related to the state of the location client connection. +
        +
        +

        + While the started {@link android.app.Service} is running, it also posts notifications with the + testing status. These notifications allow you to see status updates even if the app is not in + the foreground. When you click on a notification, the main {@link android.app.Activity} of the + provider app returns to the foreground. +

        +

        Test using the mock location provider app

        +

        + To test mock location data coming from the mock location provider app: +

        +
          +
        1. + Install the mock location provider app on a device that has Google Play services installed. + Location Services is part of Google Play services. +
        2. +
        3. + On the device, enable mock locations. To learn how to do this, see the topic + Setting up a Device for Development. +
        4. +
        5. + Start the provider app from the Launcher, then choose the options you want from the main + screen. +
        6. +
        7. + Unless you've removed the pause interval feature, the mock location provider app + pauses for a few seconds, and then starts sending mock location data to Location + Services. +
        8. +
        9. + Run the app you want to test. While the mock location provider app is running, the app + you're testing receives mock locations instead of real locations. +
        10. +
        11. + If the provider app is in the midst of a continuous test, you can switch back to real + locations by clicking Stop test. This forces the started {@link android.app.Service} + to turn off mock mode and then stop itself. When the service stops itself, the background + thread is also destroyed. +
        12. + +
        +

        Testing Tips

        +

        + The following sections contain tips for creating mock location data and using the data with a + mock location provider app. +

        +

        Choosing a send interval

        +

        + Each location provider that contributes to the fused location sent out by Location Services has + its own minimum update cycle. For example, the GPS provider can't send a new location more often + than once per second, and the Wi-Fi provider can't send a new location more often than once + every five seconds. These cycle times are handled automatically for real locations, but you + should account for them when you send mock locations. For example, you shouldn't send a new mock + location more than once per second. If you're testing indoor locations, which rely heavily on + the Wi-Fi provider, then you should consider using a send interval of five seconds. +

        +

        Simulating speed

        +

        + To simulate the speed of an actual device, shorten or lengthen the distance between two + successive locations. For example, changing the location by 88 feet every second simulates + car travel, because this change works out to 60 miles an hour. In comparison, changing the + location by 1.5 feet every second simulates brisk walking, because this change works out to + 3 miles per hour. +

        +

        Calculating location data

        +

        + By searching the web, you can find a variety of small programs that calculate a new set of + latitude and longitude coordinates from a starting location and a distance, as well as + references to formulas for calculating the distance between two points based on their latitude + and longitude. In addition, the {@link android.location.Location} class offers two methods for + calculating the distance between points: +

        +
        +
        + {@link android.location.Location#distanceBetween distanceBetween()} +
        +
        + A static method that calculates the distance between two points specified by latitude and + longitude. +
        +
        + {@link android.location.Location#distanceTo distanceTo()} +
        +
        + For a given {@link android.location.Location}, returns the distance to another + {@link android.location.Location}. +
        +
        +

        Geofence testing

        +

        + When you test an app that uses geofence detection, use test data that reflects different modes + of travel, including walking, cycling, driving, and traveling by train. For a slow mode of + travel, make small changes in position between points. Conversely, for a fast mode of travel, + make a large change in position between points. +

        +

        Managing test data

        +

        + The mock location provider app included with this lesson contains test latitude, longitude, + and accuracy values in the form of constants. You may want to consider other ways of organizing + data as well: +

        +
        +
        + XML +
        +
        + Store location data in XML files that are including in the provider app. By separating the + data from the code, you facilitate changes to the data. +
        +
        + Server download +
        +
        + Store location data on a server and then have the provider app download it. Since the data + is completely separate from the app, you can change the data without having to rebuild the + app. You can also change the data on the server and have the changes reflected immediately + in the mock locations you're testing. +
        +
        + Recorded data +
        +
        + Instead of making up test data, write a utility app that records location data as you move + the device. Use the recorded data as your test data, or use the data to guide you in + developing test data. For example, record locations as you walk with a device, and then + create mock locations that have an appropriate change in latitude and longitude over + time. +
        +
        diff --git a/docs/html/training/monitoring-device-state/connectivity-monitoring.jd b/docs/html/training/monitoring-device-state/connectivity-monitoring.jd index 11a05e18f8c369b9b43418f0d082a25044c18f6f..fb5096d3715f8880bc218e5293ea1314b1638365 100644 --- a/docs/html/training/monitoring-device-state/connectivity-monitoring.jd +++ b/docs/html/training/monitoring-device-state/connectivity-monitoring.jd @@ -49,7 +49,8 @@ to query the active network and determine if it has Internet connectivity.

        (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); -boolean isConnected = activeNetwork.isConnectedOrConnecting(); +boolean isConnected = activeNetwork != null && + activeNetwork.isConnectedOrConnecting();

        Determine the Type of your Internet Connection

        diff --git a/docs/html/training/scheduling/alarms.jd b/docs/html/training/scheduling/alarms.jd new file mode 100644 index 0000000000000000000000000000000000000000..758dc955078febedc220857849943c95204f2518 --- /dev/null +++ b/docs/html/training/scheduling/alarms.jd @@ -0,0 +1,312 @@ +page.title=Scheduling Repeating Alarms +parent.title=Using Wake Locks +parent.link=index.html + +trainingnavtop=true + +@jd:body + +
        +
        + + +

        This lesson teaches you to

        +
          +
        1. Set a Repeating Alarm
        2. +
        3. Cancel an Alarm
        4. +
        5. Start an Alarm When the Device Boots
        6. +
        + +

        Try it out

        + +
        + Download the sample +

        Scheduler.zip

        +
        + +
        +
        + +

        Alarms (based on the {@link android.app.AlarmManager} class) give you a way to perform +time-based operations outside the lifetime of your application. +For example, you could use an alarm to initiate a long-running operation, such +as starting a service once a day to download a weather forecast.

        + +

        Alarms have these characteristics:

        +
          + +
        • They let you fire Intents at set times and/or intervals.
        • + +
        • You can use them in conjunction with broadcast receivers to start services and perform +other operations.
        • + +
        • They operate outside of your application, so you can use them to trigger events or +actions even when your app is not running, and even if the device itself is asleep.
        • + +
        • They help you to minimize your app's resource requirements. You can schedule operations +without relying on timers or continuously running background services.
        • + +
        + +

        Note: For timing operations that are guaranteed to occur +during the lifetime of your application, +instead consider using the {@link android.os.Handler} class in conjunction with +{@link java.util.Timer} and {@link java.lang.Thread}. This approach gives Android better +control over system resources.

        + +

        Set a Repeating Alarm

        + +

        As described above, repeating alarms are a good choice for scheduling regular events or +data lookups. A repeating alarm has the following characteristics:

        + +
          +
        • A alarm type. For more discussion, see Choose an alarm type.
        • +
        • A trigger time. If the trigger time you specify is in the past, the alarm triggers +immediately.
        • +
        • The alarm's interval. For example, once a day, every hour, every 5 seconds, and so on.
        • +
        • A pending intent that fires when the alarm is triggered. When you set a second alarm +that uses the same pending intent, it replaces the original alarm.
        • +
        + +

        Every choice you make in designing your repeating alarm can have consequences in how your +app uses (or abuses) system resources. Even a carefully managed alarm can have a major impact +on battery life. Follow these guidelines as you design your app:

        + +
          +
        • Keep your alarm frequency to a minimum.
        • +
        • Don't wake up the device unnecessarily (this behavior is determined by the alarm type, +as described in Choose an alarm type).
        • +
        • Don't make your alarm's trigger time any more precise than it has to be: + +
            +
          • Use {@link android.app.AlarmManager#setInexactRepeating setInexactRepeating()} instead +of {@link android.app.AlarmManager#setRepeating setRepeating()} whenever possible. +When you use {@link android.app.AlarmManager#setInexactRepeating setInexactRepeating()}, +Android synchronizes multiple inexact repeating alarms and fires +them at the same time. This reduces the drain on the battery.
          • +
          • If your alarm's behavior is based on an interval (for example, your alarm +fires once an hour) rather than a precise trigger time (for example, your alarm fires at +7 a.m. sharp and every 20 minutes after that), use an {@code ELAPSED_REALTIME} +alarm type.
          • +
        • + +
        + +

        Choose an alarm type

        + +

        One of the first considerations in using a repeating alarm is what its type should be.

        + + +

        There are two general clock types for alarms: "elapsed real time" and "real time clock" +(RTC). +Elapsed real time uses the "time since system boot" as a +reference, and real time clock uses UTC (wall clock) time. This means that +elapsed real time is suited to setting an alarm based on the passage of time (for +example, an alarm that fires every 30 seconds) since it isn't affected by +time zone/locale. The real time clock type is better suited for alarms that are dependent +on current locale.

        + +

        Both types have a "wakeup" version, which says to wake up the device's CPU if the +screen is off. This ensures that the alarm will fire at the scheduled time. This is useful +if your app has a time dependency—for example, if it has a limited window to perform a +particular operation. If you don't use the wakeup version of your alarm type, then +all the repeating alarms will fire when your device is next awake.

        + +

        If you simply need your alarm to fire at a particular interval (for example, every half +hour), use one of the elapsed real time types. In general, this is the better choice.

        + +

        If you need your alarm to fire at a particular time of day, +then choose one of the clock-based real time clock types. Note, however, that this approach can +have some drawbacks—the app may not translate well to other locales, and if the user +changes the device's time setting, it could cause unexpected behavior in your app.

        + +

        Here is the list of types:

        + +
          + +
        • {@link android.app.AlarmManager#ELAPSED_REALTIME}—Fires the pending intent based +on the amount of time since the device was booted, but doesn't wake up the device. The +elapsed time includes any time during which the device was asleep.
        • + +
        • {@link android.app.AlarmManager#ELAPSED_REALTIME_WAKEUP}—Wakes up the device and +fires the pending intent after the specified length of time has elapsed since device +boot.
        • + +
        • {@link android.app.AlarmManager#RTC}—Fires the pending intent +at the specified time but does not wake up the device.
        • + +
        • {@link android.app.AlarmManager#RTC_WAKEUP}—Wakes up the +device to fire the pending intent at the specified time.
        • +
        + +

        ELAPSED_REALTIME_WAKEUP examples

        + +

        Here are some examples of using {@link android.app.AlarmManager#ELAPSED_REALTIME_WAKEUP}. +

        + +

        Wake up the device to fire the alarm in 30 minutes, and every 30 minutes +after that:

        + +
        +// Hopefully your alarm will have a lower frequency than this!
        +alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        +        AlarmManager.INTERVAL_HALF_HOUR,
        +        AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
        + +

        Wake up the device to fire a one-time (non-repeating) alarm in one minute:

        + +
        private AlarmManager alarmMgr;
        +private PendingIntent alarmIntent;
        +...
        +alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        +Intent intent = new Intent(context, AlarmReceiver.class);
        +alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
        +
        +alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        +        SystemClock.elapsedRealtime() +
        +        60 * 1000, alarmIntent);
        + + +

        RTC examples

        + +

        Here are some examples of using {@link android.app.AlarmManager#RTC_WAKEUP}.

        + +

        Wake up the device to fire the alarm at approximately 2:00 p.m., and repeat once a day +at the same time:

        + +
        // Set the alarm to start at approximately 2:00 p.m.
        +Calendar calendar = Calendar.getInstance();
        +calendar.setTimeInMillis(System.currentTimeMillis());
        +calendar.set(Calendar.HOUR_OF_DAY, 14);
        +
        +// With setInexactRepeating(), you have to use one of the AlarmManager interval
        +// constants--in this case, AlarmManager.INTERVAL_DAY.
        +alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        +        AlarmManager.INTERVAL_DAY, alarmIntent);
        + +

        Wake up the device to fire the alarm at precisely 8:30 a.m., and every 20 minutes +thereafter:

        + +
        private AlarmManager alarmMgr;
        +private PendingIntent alarmIntent;
        +...
        +alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        +Intent intent = new Intent(context, AlarmReceiver.class);
        +alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
        +
        +// Set the alarm to start at 8:30 a.m.
        +Calendar calendar = Calendar.getInstance();
        +calendar.setTimeInMillis(System.currentTimeMillis());
        +calendar.set(Calendar.HOUR_OF_DAY, 8);
        +calendar.set(Calendar.MINUTE, 30);
        +
        +// setRepeating() lets you specify a precise custom interval--in this case,
        +// 20 minutes.
        +alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        +        1000 * 60 * 20, alarmIntent);
        + +

        Decide how precise your alarm needs to be

        + +

        As described above, choosing the alarm type is often the first step in creating an alarm. +A further distinction is how precise you need your alarm to be. For most apps, +{@link android.app.AlarmManager#setInexactRepeating setInexactRepeating()} is the right +choice. +When you use this method, Android synchronizes multiple inexact repeating alarms and fires +them at the same time. This reduces the drain on the battery.

        + +

        For the rare app that has rigid time requirements—for example, the alarm needs to +fire precisely at 8:30 a.m., and every hour on the hour +thereafter—use {@link android.app.AlarmManager#setRepeating setRepeating()}. But you +should avoid using exact alarms if possible.

        + +

        With {@link android.app.AlarmManager#setInexactRepeating setInexactRepeating()}, +you can't specify a custom interval the way you can with +{@link android.app.AlarmManager#setRepeating setRepeating()}. You have to use one of the +interval constants, such as {@link android.app.AlarmManager#INTERVAL_FIFTEEN_MINUTES}, +{@link android.app.AlarmManager#INTERVAL_DAY}, and so on. See {@link android.app.AlarmManager} +for the complete list. +

        + +

        Cancel an Alarm

        + +

        Depending on your app, you may want to include the ability to cancel the alarm. +To cancel an alarm, call {@link android.app.AlarmManager#cancel cancel()} on the Alarm +Manager, passing in the {@link android.app.PendingIntent} you no longer want to fire. For +example:

        + +
        // If the alarm has been set, cancel it.
        +if (alarmMgr!= null) {
        +    alarmMgr.cancel(alarmIntent);
        +}
        + +

        Start an Alarm When the Device Boots

        + +

        By default, all alarms are canceled when a device shuts down. +To prevent this from happening, you can design your application +to automatically restart a repeating alarm if the user reboots the device. This ensures +that the {@link android.app.AlarmManager} will continue doing its task without the user +needing to manually restart the alarm.

        + + +

        Here are the steps:

        +
          +
        1. Set the +{@code RECEIVE_BOOT_COMPLETED} permission in your application's manifest. This allows +your app to receive the +{@link android.content.Intent#ACTION_BOOT_COMPLETED} that is broadcast after the system +finishes booting (this only works if the app has already been launched by the user at least once): +
          +<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
          +
        2. + +
        3. Implement a {@link android.content.BroadcastReceiver} to receive the broadcast: +
          public class SampleBootReceiver extends BroadcastReceiver {
          +
          +    @Override
          +    public void onReceive(Context context, Intent intent) {
          +        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
          +            // Set the alarm here.
          +        }
          +    }
          +}
        4. + +
        5. Add the receiver to your app's manifest file with an intent filter that filters on +the {@link android.content.Intent#ACTION_BOOT_COMPLETED} action: + +
          <receiver android:name=".SampleBootReceiver"
          +        android:enabled="false">
          +    <intent-filter>
          +        <action android:name="android.intent.action.BOOT_COMPLETED"></action>
          +    </intent-filter>
          +</receiver>
          + + +

          Notice that in the manifest, the boot receiver is set to +{@code android:enabled="false"}. This means that the receiver will not be called +unless the application explicitly enables it. This prevents the boot receiver from being +called unnecessarily. You can enable a receiver (for example, if the user sets an alarm) +as follows:

          + +
          ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
          +PackageManager pm = context.getPackageManager();
          +
          +pm.setComponentEnabledSetting(receiver,
          +        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
          +        PackageManager.DONT_KILL_APP);
          +
          + +

          Once you enable the receiver this way, it will stay enabled, even if the user reboots +the device. In other words, programmatically enabling the receiver overrides the +manifest setting, even across reboots. The receiver will stay enabled until your app disables it. +You can disable a receiver (for example, if the user cancels an alarm) as follows:

          + +
          ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
          +PackageManager pm = context.getPackageManager();
          +
          +pm.setComponentEnabledSetting(receiver,
          +        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
          +        PackageManager.DONT_KILL_APP);
          + +
        6. +
        diff --git a/docs/html/training/scheduling/index.jd b/docs/html/training/scheduling/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..9ffbc16a2c9920df30721b18c85dfc4a62728360 --- /dev/null +++ b/docs/html/training/scheduling/index.jd @@ -0,0 +1,66 @@ +page.title=Managing Device Awake State +page.tags="" + +trainingnavtop=true +startpage=true + + +@jd:body +
        +
        + + +

        Dependencies and prerequisites

        + +
          +
        • Android 1.6 (API Level 4) or higher
        • +
        + +

        Try it out

        + +
        + Download the sample +

        Scheduler.zip

        +
        + +
        +
        + +

        +When an Android device is left idle, it will first dim, then turn off the screen, and +ultimately turn off the CPU. This prevents the device's battery from quickly getting +drained. Yet there are times when your application might require a different behavior:

        + +
          + +
        • Apps such as games or movie apps may need to keep the screen turned on.

          + +
        • Other applications may not need the screen to remain on, but they may require the CPU + to keep running until a critical operation finishes.

          + +
        + +

        +This class describes how to keep a device awake when necessary without draining +its battery. +

        +

        Lessons

        + +
        +
        + Keeping the Device Awake +
        +
        + Learn how to keep the screen or CPU awake as needed, while minimizing the impact + on battery life. +
        +
        + Scheduling Repeating Alarms +
        +
        + Learn how to use repeating alarms to schedule operations that take place outside + of the lifetime of the application, even if the application is not running and/or the + device is asleep. +
        +
        diff --git a/docs/html/training/scheduling/wakelock.jd b/docs/html/training/scheduling/wakelock.jd new file mode 100644 index 0000000000000000000000000000000000000000..0fab7be2cbc8a829f5d038f5f1dccc61980ef7a2 --- /dev/null +++ b/docs/html/training/scheduling/wakelock.jd @@ -0,0 +1,215 @@ +page.title=Keeping the Device Awake + +trainingnavtop=true + +@jd:body + +
        +
        + + +

        This lesson teaches you to

        +
          +
        1. Keep the Screen On
        2. +
        3. Keep the CPU On
        4. +
        + +

        Try it out

        + +
        + Download the sample +

        Scheduler.zip

        +
        + +
        +
        + + +

        To avoid draining the battery, an Android device that is left idle quickly falls asleep. +However, there are times when an application needs to wake up the screen or the CPU +and keep it awake to complete some work.

        + +

        The approach you take depends on the needs of your app. However, a general rule of thumb +is that you should use the most lightweight approach possible for your app, to minimize your +app's impact on system resources. The following sections describe how to handle the cases +where the device's default sleep behavior is incompatible with the requirements of your app.

        + +

        Keep the Screen On

        + +

        Certain apps need to keep the screen turned on, such as games or movie apps. The best +way to do this is to use the +{@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON FLAG_KEEP_SCREEN_ON} +in your activity (and only in an activity, never in a service or +other app component). For example:

        + +
        public class MainActivity extends Activity {
        +  @Override
        +  protected void onCreate(Bundle savedInstanceState) {
        +    super.onCreate(savedInstanceState);
        +    setContentView(R.layout.activity_main);
        +    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        +  }
        + +

        The advantage of this approach is that unlike wake locks (discussed in +Keep the CPU On), it doesn't require special permission, and the platform correctly +manages the user moving between applications, without your app needing to worry about +releasing unused resources.

        + +

        Another way to implement this is in your application's layout XML file, by using the +{@link android.R.attr#keepScreenOn android:keepScreenOn} attribute:

        + +
        <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        +    android:layout_width="match_parent"
        +    android:layout_height="match_parent"
        +    android:keepScreenOn="true">
        +    ...
        +</RelativeLayout>
        + +

        Using android:keepScreenOn="true" is equivalent to using +{@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON FLAG_KEEP_SCREEN_ON}. +You can use whichever approach is best for your app. The advantage of setting the flag +programmatically in your activity is that it gives you the option of programmatically +clearing the flag later and thereby allowing the screen to turn off.

        + +

        Note: You don't need to clear the +{@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON FLAG_KEEP_SCREEN_ON} +flag unless you no longer want the screen to +stay on in your running application (for example, if you want the screen to time out +after a certain period of inactivity). The window manager takes care of +ensuring that the right things happen when the app goes into the background or returns to +the foreground. But if you want to explicitly clear the flag and thereby allow the screen to +turn off again, use {@link android.view.Window#clearFlags clearFlags()}: +{@code getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)}.

        + +

        Keep the CPU On

        + + + + +

        If you need to keep the CPU running in order to complete some work before the device goes +to sleep, you can use a {@link android.os.PowerManager} system service feature called +wake locks. Wake locks allow your application to control the power state of the host device.

        + +

        Creating and holding wake locks can have a dramatic impact on the host device's battery +life. Thus you should use wake locks only when strictly necessary +and hold them for as short a time as possible. For example, you should never need to use a +wake lock in an activity. As described above, if you want +to keep the screen on in your activity, use +{@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON FLAG_KEEP_SCREEN_ON}.

        + + +

        One legitimate case for using a wake lock might be a background service +that needs to grab a wake lock to keep the CPU running to do work while the screen is off. +Again, though, this practice should be minimized because of its impact on battery life.

        + +

        To use a wake lock, the first step is to add the {@link android.Manifest.permission#WAKE_LOCK} + permission to your application's manifest file:

        + +
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        + +

        If your app includes a broadcast receiver that uses a service to do some +work, you can manage your wake lock through a +{@link android.support.v4.content.WakefulBroadcastReceiver}, as described in +Using a WakefulBroadcastReceiver. This is the preferred approach. +If your app doesn't follow that pattern, here is how you set a wake lock +directly:

        + +
        +PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        +Wakelock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
        +        "MyWakelockTag");
        +wakeLock.acquire();
        + +

        To release the wake lock, call +{@link android.os.PowerManager.WakeLock#release wakelock.release()}. This releases your +claim to the CPU. It's important to release a wake lock as soon as your app is finished +using it to avoid draining the battery.

        + +

        Using WakefulBroadcastReceiver

        + +

        Using a broadcast receiver in conjunction with a service lets you manage the life cycle +of a background task.

        + +

        A {@link android.support.v4.content.WakefulBroadcastReceiver} is a special type of +broadcast receiver that takes care of +creating and managing a +{@link android.os.PowerManager#PARTIAL_WAKE_LOCK} for your app. A +{@link android.support.v4.content.WakefulBroadcastReceiver} +passes off the work to a {@link android.app.Service} +(typically an +{@link android.app.IntentService}), while ensuring that the device does not +go back to sleep in the transition. If you don't hold a wake lock while transitioning +the work to a service, you are effectively allowing the device to go back to sleep before +the work completes. The net result is that the app might not finish doing the work until +some arbitrary point in the future, which is not what you want.

        + +

        The first step in using a +{@link android.support.v4.content.WakefulBroadcastReceiver} is to add it to your +manifest, as with any other broadcast receiver:

        + +
        <receiver android:name=".MyWakefulReceiver"></receiver>
        + +

        The following code starts {@code MyIntentService} with the method +{@link android.support.v4.content.WakefulBroadcastReceiver#startWakefulService startWakefulService()}. +This method is comparable to {@link android.content.Context#startService startService()}, except that +the {@link android.support.v4.content.WakefulBroadcastReceiver} is holding a +wake lock when the service starts. The intent that is passed with +{@link android.support.v4.content.WakefulBroadcastReceiver#startWakefulService startWakefulService()} +holds an extra identifying the wake lock:

        + +
        public class MyWakefulReceiver extends WakefulBroadcastReceiver {
        +
        +    @Override
        +    public void onReceive(Context context, Intent intent) {
        +
        +        // Start the service, keeping the device awake while the service is
        +        // launching. This is the Intent to deliver to the service.
        +        Intent service = new Intent(context, MyIntentService.class);
        +        startWakefulService(context, service);
        +    }
        +}
        + +

        When the service is finished, it calls +{@link android.support.v4.content.WakefulBroadcastReceiver#completeWakefulIntent MyWakefulReceiver.completeWakefulIntent()} +to release the wake lock. The +{@link android.support.v4.content.WakefulBroadcastReceiver#completeWakefulIntent completeWakefulIntent()} +method has as its parameter the same intent that was +passed in from the {@link android.support.v4.content.WakefulBroadcastReceiver}:

        +
        +public class MyIntentService extends IntentService {
        +    public static final int NOTIFICATION_ID = 1;
        +    private NotificationManager mNotificationManager;
        +    NotificationCompat.Builder builder;
        +    public MyIntentService() {
        +        super("MyIntentService");
        +    }
        +    @Override
        +    protected void onHandleIntent(Intent intent) {
        +        Bundle extras = intent.getExtras();
        +        // Do the work that requires your app to keep the CPU running.
        +        // ...
        +        // Release the wake lock provided by the WakefulBroadcastReceiver.
        +        MyWakefulReceiver.completeWakefulIntent(intent);
        +    }
        +}
        diff --git a/docs/html/training/secure-file-sharing/index.jd b/docs/html/training/secure-file-sharing/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..aa009fc2caa5007d3557e7ce028f1a5a73c497ce --- /dev/null +++ b/docs/html/training/secure-file-sharing/index.jd @@ -0,0 +1,81 @@ +page.title=Sharing Files +page.tags="FileProvider","share","ContentProvider" + +trainingnavtop=true +startpage=true + + +@jd:body + +
        +
        + +

        Dependencies and prerequisites

        +
          +
        • Android 1.6 (API Level 4) or higher
        • +
        • Familiarity with file operations such as opening, reading, and writing files
        • +
        + +

        You should also read

        + + +
        +
        + +

        + Apps often have a need to offer one or more of their files to another app. For example, an image + gallery may want to offer files to image editors, or a file management app may want to allow + users to copy and paste files between areas in external storage. One way a sending app can + share a file is to respond to a request from the receiving app. +

        +

        + In all cases, the only secure way to offer a file from your app to another app is to send the + receiving app the file's content URI and grant temporary access permissions to that URI. + Content URIs with temporary URI access permissions are secure because they apply only to the + app that receives the URI, and they expire automatically. The Android + {@link android.support.v4.content.FileProvider} component provides the method + {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()} for + generating a file's content URI. +

        +

        + If you want to share small amounts of text or numeric data between apps, you should send an + {@link android.content.Intent} that contains the data. To learn how to send simple data with an + {@link android.content.Intent}, see the training class + Sharing Simple Data. +

        +

        + This class explains how to securely share files from your app to another app using content URIs + generated by the Android {@link android.support.v4.content.FileProvider} component and + temporary permissions that you grant to the receiving app for the content URI. +

        +

        Lessons

        +
        +
        Setting Up File Sharing
        +
        + Learn how to set up your app to share files. +
        +
        Sharing a File
        +
        + Learn how to offer a file to another app by generating a content URI for the file, + granting access permissions to the URI, and sending the URI to the app. +
        +
        Requesting a Shared File
        +
        + Learn how to request a file shared by another app, receive the content URI for the file, + and use the content URI to open the file. +
        +
        + Retrieving File Information +
        +
        + Learn how an app can use a content URI generated by a + {@link android.support.v4.content.FileProvider} to retrieve file information including + MIME type and file size. +
        +
        + + diff --git a/docs/html/training/secure-file-sharing/request-file.jd b/docs/html/training/secure-file-sharing/request-file.jd new file mode 100644 index 0000000000000000000000000000000000000000..116701dcddc203c4a0b74a4ddcb451d7d46c77e0 --- /dev/null +++ b/docs/html/training/secure-file-sharing/request-file.jd @@ -0,0 +1,147 @@ +page.title=Requesting a Shared File + +trainingnavtop=true +@jd:body + + +
        +
        + +

        This lesson teaches you to

        +
          +
        1. Send a Request for the File
        2. +
        3. Access the Requested File +
        + +

        You should also read

        + + +
        +
        + +

        + When an app wants to access a file shared by another app, the requesting app (the client) + usually sends a request to the app sharing the files (the server). In most cases, the request + starts an {@link android.app.Activity} in the server app that displays the files it can share. + The user picks a file, after which the server app returns the file's content URI to the + client app. +

        +

        + This lesson shows you how a client app requests a file from a server app, receives the file's + content URI from the server app, and opens the file using the content URI. +

        + +

        Send a Request for the File

        +

        + To request a file from the server app, the client app calls + {@link android.app.Activity#startActivityForResult startActivityForResult} with an + {@link android.content.Intent} containing the action such as + {@link android.content.Intent#ACTION_PICK ACTION_PICK} and a MIME type that the client app + can handle. +

        +

        + For example, the following code snippet demonstrates how to send an + {@link android.content.Intent} to a server app in order to start the + {@link android.app.Activity} described in Sharing a File: +

        +
        +public class MainActivity extends Activity {
        +    private Intent mRequestFileIntent;
        +    private ParcelFileDescriptor mInputPFD;
        +    ...
        +    @Override
        +    protected void onCreate(Bundle savedInstanceState) {
        +        super.onCreate(savedInstanceState);
        +        setContentView(R.layout.activity_main);
        +        mRequestFileIntent = new Intent(Intent.ACTION_PICK);
        +        mRequestFileIntent.setType("image/jpg");
        +        ...
        +    }
        +    ...
        +    protected void requestFile() {
        +        /**
        +         * When the user requests a file, send an Intent to the
        +         * server app.
        +         * files.
        +         */
        +            startActivityForResult(mRequestFileIntent, 0);
        +        ...
        +    }
        +    ...
        +}
        +
        +

        Access the Requested File

        +

        + The server app sends the file's content URI back to the client app in an + {@link android.content.Intent}. This {@link android.content.Intent} is passed to the client + app in its override of {@link android.app.Activity#onActivityResult onActivityResult()}. Once + the client app has the file's content URI, it can access the file by getting its + {@link java.io.FileDescriptor}. +

        +

        +

        + File security is preserved in this process because the content URI is the only piece of data + that the client app receives. Since this URI doesn't contain a directory path, the client app + can't discover and open any other files in the server app. Only the client app gets access to + the file, and only for the permissions granted by the server app. The permissions are temporary, + so once the client app's task stack is finished, the file is no longer accessible outside the + server app. +

        +

        + The next snippet demonstrates how the client app handles the + {@link android.content.Intent} sent from the server app, and how the client app gets the + {@link java.io.FileDescriptor} using the content URI: +

        +
        +    /*
        +     * When the Activity of the app that hosts files sets a result and calls
        +     * finish(), this method is invoked. The returned Intent contains the
        +     * content URI of a selected file. The result code indicates if the
        +     * selection worked or not.
        +     */
        +    @Override
        +    public void onActivityResult(int requestCode, int resultCode,
        +            Intent returnIntent) {
        +        // If the selection didn't work
        +        if (resultCode != RESULT_OK) {
        +            // Exit without doing anything else
        +            return;
        +        } else {
        +            // Get the file's content URI from the incoming Intent
        +            Uri returnUri = returnIntent.getData();
        +            /*
        +             * Try to open the file for "read" access using the
        +             * returned URI. If the file isn't found, write to the
        +             * error log and return.
        +             */
        +            try {
        +                /*
        +                 * Get the content resolver instance for this context, and use it
        +                 * to get a ParcelFileDescriptor for the file.
        +                 */
        +                mInputPFD = getContentResolver().openFileDescriptor(returnUri, "r");
        +            } catch (FileNotFoundException e) {
        +                e.printStackTrace();
        +                Log.e("MainActivity", "File not found.");
        +                return;
        +            }
        +            // Get a regular file descriptor for the file
        +            FileDescriptor fd = mInputPFD.getFileDescriptor();
        +            ...
        +        }
        +    }
        +
        +

        + The method {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()} + returns a {@link android.os.ParcelFileDescriptor} for the file. From this object, the client + app gets a {@link java.io.FileDescriptor} object, which it can then use to read the file. +

        diff --git a/docs/html/training/secure-file-sharing/retrieve-info.jd b/docs/html/training/secure-file-sharing/retrieve-info.jd new file mode 100644 index 0000000000000000000000000000000000000000..4a2b7d86b4a04895a75d7758c95b780111908ee3 --- /dev/null +++ b/docs/html/training/secure-file-sharing/retrieve-info.jd @@ -0,0 +1,110 @@ +page.title=Retrieving File Information + +trainingnavtop=true +@jd:body + + +
        +
        + + +

        This lesson teaches you to

        +
          +
        1. Retrieve a File's MIME Type
        2. +
        3. Retrieve a File's Name and Size
        4. +
        + + +

        You should also read

        + + +
        +
        +

        + Before a client app tries to work with a file for which it has a content URI, the app can + request information about the file from the server app, including the file's data type and + file size. The data type helps the client app to determine if it can handle the file, and the + file size helps the client app set up buffering and caching for the file. +

        +

        + This lesson demonstrates how to query the server app's + {@link android.support.v4.content.FileProvider} to retrieve a file's MIME type and size. +

        +

        Retrieve a File's MIME Type

        +

        + A file's data type indicates to the client app how it should handle the file's contents. To get + the data type of a shared file given its content URI, the client app calls + {@link android.content.ContentResolver#getType ContentResolver.getType()}. This method returns + the file's MIME type. By default, a + {@link android.support.v4.content.FileProvider} determines the file's MIME type from its + filename extension. +

        +

        + The following code snippet demonstrates how a client app retrieves the MIME type of a file once + the server app has returned the content URI to the client: +

        +
        +    ...
        +    /*
        +     * Get the file's content URI from the incoming Intent, then
        +     * get the file's MIME type
        +     */
        +    Uri returnUri = returnIntent.getData();
        +    String mimeType = getContentResolver().getType(returnUri);
        +    ...
        +
        +

        Retrieve a File's Name and Size

        +

        + The {@link android.support.v4.content.FileProvider} class has a default implementation of the + {@link android.support.v4.content.FileProvider#query query()} method that returns the + name and size of the file associated with a content URI in a + {@link android.database.Cursor}. The default implementation returns two columns: +

        +
        +
        {@link android.provider.OpenableColumns#DISPLAY_NAME DISPLAY_NAME}
        +
        + The file's name, as a {@link java.lang.String}. This value is the same as the value returned + by {@link java.io.File#getName File.getName()}. +
        +
        {@link android.provider.OpenableColumns#SIZE SIZE}
        +
        + The size of the file in bytes, as a {@code long} This value is the same as the value + returned by {@link java.io.File#length File.length()} +
        +
        +

        + The client app can get both the {@link android.provider.OpenableColumns#DISPLAY_NAME + DISPLAY_NAME} and {@link android.provider.OpenableColumns#SIZE SIZE} for a file by setting all + of the arguments of {@link android.support.v4.content.FileProvider#query query()} to + {@code null} except for the content URI. For example, this code snippet retrieves a file's + {@link android.provider.OpenableColumns#DISPLAY_NAME DISPLAY_NAME} and + {@link android.provider.OpenableColumns#SIZE SIZE} and displays each one in separate + {@link android.widget.TextView}: +

        +
        +    ...
        +    /*
        +     * Get the file's content URI from the incoming Intent,
        +     * then query the server app to get the file's display name
        +     * and size.
        +     */
        +    Uri returnUri = returnIntent.getData();
        +    Cursor returnCursor =
        +            getContentResolver().query(returnUri, null, null, null, null);
        +    /*
        +     * Get the column indexes of the data in the Cursor,
        +     * move to the first row in the Cursor, get the data,
        +     * and display it.
        +     */
        +    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        +    int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
        +    returnCursor.moveToFirst();
        +    TextView nameView = (TextView) findViewById(R.id.filename_text);
        +    TextView sizeView = (TextView) findViewById(R.id.filesize_text);
        +    nameView.setText(returnCursor.getString(nameIndex));
        +    sizeView.setText(Long.toString(returnCursor.getLong(sizeIndex)));
        +    ...
        +
        diff --git a/docs/html/training/secure-file-sharing/setup-sharing.jd b/docs/html/training/secure-file-sharing/setup-sharing.jd new file mode 100644 index 0000000000000000000000000000000000000000..8c8fa0ff6cc1274e093a1eb3f0255886fd70e688 --- /dev/null +++ b/docs/html/training/secure-file-sharing/setup-sharing.jd @@ -0,0 +1,144 @@ +page.title=Setting Up File Sharing +trainingnavtop=true +@jd:body + + +
        +
        + + +

        This lesson teaches you to

        +
          +
        1. Specify the FileProvider
        2. +
        3. Specify Sharable Directories
        4. +
        + +

        You should also read

        + + +
        +
        + +

        + To securely offer a file from your app to another app, you need to configure your app to offer + a secure handle to the file, in the form of a content URI. The Android + {@link android.support.v4.content.FileProvider} component generates content URIs for + files, based on specifications you provide in XML. This lesson shows you how to add the default + implementation of {@link android.support.v4.content.FileProvider} to your app, and how to + specify the files you want to offer to other apps. +

        + +

        + Note: The {@link android.support.v4.content.FileProvider} class is part of the + v4 Support Library. For information + about including this library in your application, see + Support Library Setup. +

        + +

        Specify the FileProvider

        +

        + Defining a {@link android.support.v4.content.FileProvider} for your app requires an entry in + your manifest. This entry specifies the authority to use in generating content URIs, as well as + the name of an XML file that specifies the directories your app can share. +

        +

        + The following snippet shows you how to add to your manifest the + <provider> element that specifies the + {@link android.support.v4.content.FileProvider} class, the authority, and the + XML file name: +

        +
        +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        +    package="com.example.myapp">
        +    <application
        +        ...>
        +        <provider
        +            android:name="android.support.v4.content.FileProvider"
        +            android:authorities="com.example.myapp.fileprovider"
        +            android:grantUriPermissions="true"
        +            android:exported="false">
        +            <meta-data
        +                android:name="android.support.FILE_PROVIDER_PATHS"
        +                android:resource="@xml/filepaths" />
        +        </provider>
        +        ...
        +    </application>
        +</manifest>
        +

        + In this example, the android:authorities attribute specifies the URI authority + that you want to use for content URIs generated by the + {@link android.support.v4.content.FileProvider}. + In the example, the authority is com.example.myapp.fileprovider. For your own + app, specify an authority consisting of the app's + android:package value with the string "fileprovider" appended to it. To learn more + about the authority value, see the topic + Content URIs and the documentation for the + android:authorities attribute. +

        +

        + The <meta-data> child element of the + <provider> points to an XML file that specifies the directories you want to + share. The android:resource attribute is the path and name of the file, without + the .xml extension.The contents of this file are described in the next section. +

        +

        Specify Sharable Directories

        +

        + Once you have added the {@link android.support.v4.content.FileProvider} to your app manifest, + you need to specify the directories that contain the files you want to share. To specify the + directories, start by creating the file filepaths.xml in the res/xml/ + subdirectory of your project. In this file, specify the directories by adding an XML element for + each directory. The following snippet shows you an example of the contents of + res/xml/filepaths.xml. The snippet also demonstrates how to share a subdirectory + of the files/ directory in your internal storage area: +

        +
        +<paths>
        +    <files-path path="images/" name="myimages" />
        +</paths>
        +

        + In this example, the <files-path> tag shares directories within the + files/ directory of your app's internal storage. The path attribute + shares the images/ subdirectory of files/. The name + attribute tells the {@link android.support.v4.content.FileProvider} to add the path segment + myimages to content URIs for files in the files/images/ subdirectory. +

        +

        + The <paths> element can have multiple children, each specifying a different + directory to share. In addition to the <files-path> element, you can + use the <external-path> element to share directories in external storage, and + the <cache-path> element to share directories in your internal cache + directory. To learn more about the child elements that specify shared directories, see the + {@link android.support.v4.content.FileProvider} reference documentation. +

        +

        + Note: The XML file is the only way you can specify the directories you want to + share; you can't programmatically add a directory. +

        +

        + You now have a complete specification of a {@link android.support.v4.content.FileProvider} + that generates content URIs for files in the files/ directory of your app's + internal storage or for files in subdirectories of files/. When your app generates + a content URI for a file, it contains the authority specified in the + <provider> element (com.example.myapp.fileprovider), + the path myimages/, and the name of the file. +

        +

        + For example, if you define a {@link android.support.v4.content.FileProvider} according to the + snippets in this lesson, and you request a content URI for the file + default_image.jpg, {@link android.support.v4.content.FileProvider} returns the + following URI: +

        +
        +content://com.example.myapp.fileprovider/myimages/default_image.jpg
        + diff --git a/docs/html/training/secure-file-sharing/share-file.jd b/docs/html/training/secure-file-sharing/share-file.jd new file mode 100644 index 0000000000000000000000000000000000000000..6c52770a0fdaf2cde5334423f8582e3c8f03e5b9 --- /dev/null +++ b/docs/html/training/secure-file-sharing/share-file.jd @@ -0,0 +1,298 @@ +page.title=Sharing a File + +trainingnavtop=true +@jd:body + + + +

        + Once you have set up your app to share files using content URIs, you can respond to other apps' + requests for those files. One way to respond to these requests is to provide a file selection + interface from the server app that other applications can invoke. This approach allows a client + application to let users select a file from the server app and then receive the selected file's + content URI. +

        +

        + This lesson shows you how to create a file selection {@link android.app.Activity} in your app + that responds to requests for files. +

        +

        Receive File Requests

        +

        + To receive requests for files from client apps and respond with a content URI, your app should + provide a file selection {@link android.app.Activity}. Client apps start this + {@link android.app.Activity} by calling {@link android.app.Activity#startActivityForResult + startActivityForResult()} with an {@link android.content.Intent} containing the action + {@link android.content.Intent#ACTION_PICK ACTION_PICK}. When the client app calls + {@link android.app.Activity#startActivityForResult startActivityForResult()}, your app can + return a result to the client app, in the form of a content URI for the file the user selected. +

        +

        + To learn how to implement a request for a file in a client app, see the lesson + Requesting a Shared File. +

        +

        Create a File Selection Activity

        +

        + To set up the file selection {@link android.app.Activity}, start by specifying the + {@link android.app.Activity} in your manifest, along with an intent filter + that matches the action {@link android.content.Intent#ACTION_PICK ACTION_PICK} and the + categories {@link android.content.Intent#CATEGORY_DEFAULT CATEGORY_DEFAULT} and + {@link android.content.Intent#CATEGORY_OPENABLE CATEGORY_OPENABLE}. Also add MIME type filters + for the files your app serves to other apps. The following snippet shows you how to specify the + new {@link android.app.Activity} and intent filter: +

        +
        +<manifest xmlns:android="http://schemas.android.com/apk/res/android">
        +    ...
        +        <application>
        +        ...
        +            <activity
        +                android:name=".FileSelectActivity"
        +                android:label="@"File Selector" >
        +                <intent-filter>
        +                    <action
        +                        android:name="android.intent.action.PICK"/>
        +                    <category
        +                        android:name="android.intent.category.DEFAULT"/>
        +                    <category
        +                        android:name="android.intent.category.OPENABLE"/>
        +                    <data android:mimeType="text/plain"/>
        +                    <data android:mimeType="image/*"/>
        +                </intent-filter>
        +            </activity>
        +

        Define the file selection Activity in code

        +

        + Next, define an {@link android.app.Activity} subclass that displays the files available from + your app's files/images/ directory in internal storage and allows the user to pick + the desired file. The following snippet demonstrates how to define this + {@link android.app.Activity} and respond to the user's selection: +

        +
        +public class MainActivity extends Activity {
        +    // The path to the root of this app's internal storage
        +    private File mPrivateRootDir;
        +    // The path to the "images" subdirectory
        +    private File mImagesDir;
        +    // Array of files in the images subdirectory
        +    File[] mImageFiles;
        +    // Array of filenames corresponding to mImageFiles
        +    String[] mImageFilenames;
        +    // Initialize the Activity
        +    @Override
        +    protected void onCreate(Bundle savedInstanceState) {
        +        ...
        +        // Set up an Intent to send back to apps that request a file
        +        mResultIntent =
        +                new Intent("com.example.myapp.ACTION_RETURN_FILE");
        +        // Get the files/ subdirectory of internal storage
        +        mPrivateRootDir = getFilesDir();
        +        // Get the files/images subdirectory;
        +        mImagesDir = new File(mPrivateRootDir, "images");
        +        // Get the files in the images subdirectory
        +        mImageFiles = mImagesDir.listFiles();
        +        // Set the Activity's result to null to begin with
        +        setResult(Activity.RESULT_CANCELED, null);
        +        /*
        +         * Display the file names in the ListView mFileListView.
        +         * Back the ListView with the array mImageFilenames, which
        +         * you can create by iterating through mImageFiles and
        +         * calling File.getAbsolutePath() for each File
        +         */
        +         ...
        +    }
        +    ...
        +}
        +

        Respond to a File Selection

        +

        + Once a user selects a shared file, your application must determine what file was selected and + then generate a content URI for the file. Since the {@link android.app.Activity} displays the + list of available files in a {@link android.widget.ListView}, when the user clicks a file name + the system calls the method {@link android.widget.AdapterView.OnItemClickListener#onItemClick + onItemClick()}, in which you can get the selected file. +

        +

        + In {@link android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()}, get a + {@link java.io.File} object for the file name of the selected file and pass it as an argument to + {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()}, along with the + authority that you specified in the + <provider> element for the {@link android.support.v4.content.FileProvider}. + The resulting content URI contains the authority, a path segment corresponding to the file's + directory (as specified in the XML meta-data), and the name of the file including its + extension. How {@link android.support.v4.content.FileProvider} maps directories to path + segments based on XML meta-data is described in the section + Specify Sharable Directories. +

        +

        + The following snippet shows you how to detect the selected file and get a content URI for it: +

        +
        +    protected void onCreate(Bundle savedInstanceState) {
        +        ...
        +        // Define a listener that responds to clicks on a file in the ListView
        +        mFileListView.setOnItemClickListener(
        +                new AdapterView.OnItemClickListener() {
        +            @Override
        +            /*
        +             * When a filename in the ListView is clicked, get its
        +             * content URI and send it to the requesting app
        +             */
        +            public void onItemClick(AdapterView<?> adapterView,
        +                    View view,
        +                    int position,
        +                    long rowId) {
        +                /*
        +                 * Get a File for the selected file name.
        +                 * Assume that the file names are in the
        +                 * mImageFilename array.
        +                 */
        +                File requestFile = new File(mImageFilename[position]);
        +                /*
        +                 * Most file-related method calls need to be in
        +                 * try-catch blocks.
        +                 */
        +                // Use the FileProvider to get a content URI
        +                try {
        +                    fileUri = FileProvider.getUriForFile(
        +                            MainActivity.this,
        +                            "com.example.myapp.fileprovider",
        +                            requestFile);
        +                } catch (IllegalArgumentException e) {
        +                    Log.e("File Selector",
        +                          "The selected file can't be shared: " +
        +                          clickedFilename);
        +                }
        +                ...
        +            }
        +        });
        +        ...
        +    }
        +

        + Remember that you can only generate content URIs for files that reside in a directory + you've specified in the meta-data file that contains the <paths> element, as + described in the section Specify Sharable Directories. If you call + {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()} for a + {@link java.io.File} in a path that you haven't specified, you receive an + {@link java.lang.IllegalArgumentException}. +

        +

        Grant Permissions for the File

        +

        + Now that you have a content URI for the file you want to share with another app, you need to + allow the client app to access the file. To allow access, grant permissions to the client app by + adding the content URI to an {@link android.content.Intent} and then setting permission flags on + the {@link android.content.Intent}. The permissions you grant are temporary and expire + automatically when the receiving app's task stack is finished. +

        +

        + The following code snippet shows you how to set read permission for the file: +

        +
        +    protected void onCreate(Bundle savedInstanceState) {
        +        ...
        +        // Define a listener that responds to clicks in the ListView
        +        mFileListView.setOnItemClickListener(
        +                new AdapterView.OnItemClickListener() {
        +            @Override
        +            public void onItemClick(AdapterView<?> adapterView,
        +                    View view,
        +                    int position,
        +                    long rowId) {
        +                ...
        +                if (fileUri != null) {
        +                    // Grant temporary read permission to the content URI
        +                    mResultIntent.addFlags(
        +                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
        +                }
        +                ...
        +             }
        +             ...
        +        });
        +    ...
        +    }
        +

        + Caution: Calling {@link android.content.Intent#setFlags setFlags()} is the only + way to securely grant access to your files using temporary access permissions. Avoid calling + {@link android.content.Context#grantUriPermission Context.grantUriPermission()} method for a + file's content URI, since this method grants access that you can only revoke by + calling {@link android.content.Context#revokeUriPermission Context.revokeUriPermission()}. +

        +

        Share the File with the Requesting App

        +

        + To share the file with the app that requested it, pass the {@link android.content.Intent} + containing the content URI and permissions to {@link android.app.Activity#setResult + setResult()}. When the {@link android.app.Activity} you have just defined is finished, the + system sends the {@link android.content.Intent} containing the content URI to the client app. + The following code snippet shows you how to do this: +

        +
        +    protected void onCreate(Bundle savedInstanceState) {
        +        ...
        +        // Define a listener that responds to clicks on a file in the ListView
        +        mFileListView.setOnItemClickListener(
        +                new AdapterView.OnItemClickListener() {
        +            @Override
        +            public void onItemClick(AdapterView<?> adapterView,
        +                    View view,
        +                    int position,
        +                    long rowId) {
        +                ...
        +                if (fileUri != null) {
        +                    ...
        +                    // Put the Uri and MIME type in the result Intent
        +                    mResultIntent.setDataAndType(
        +                            fileUri,
        +                            getContentResolver().getType(fileUri));
        +                    // Set the result
        +                    MainActivity.this.setResult(Activity.RESULT_OK,
        +                            mResultIntent);
        +                    } else {
        +                        mResultIntent.setDataAndType(null, "");
        +                        MainActivity.this.setResult(RESULT_CANCELED,
        +                                mResultIntent);
        +                    }
        +                }
        +        });
        +

        + Provide users with an way to return immediately to the client app once they have chosen a file. + One way to do this is to provide a checkmark or Done button. Associate a method with + the button using the button's + android:onClick attribute. In the method, call + {@link android.app.Activity#finish finish()}. For example: +

        +
        +    public void onDoneClick(View v) {
        +        // Associate a method with the Done button
        +        finish();
        +    }
        diff --git a/docs/html/training/sharing/index.jd b/docs/html/training/sharing/index.jd index 2aa22b6a0490be415c6e35fd7556984a092d5700..06d42fc81887e02dfaf506178ad155f0cdf71e03 100644 --- a/docs/html/training/sharing/index.jd +++ b/docs/html/training/sharing/index.jd @@ -1,4 +1,4 @@ -page.title=Sharing Content +page.title=Sharing Simple Data page.tags="intents","share" trainingnavtop=true @@ -20,26 +20,26 @@ Intent Filters - +

        One of the great things about Android applications is their ability to communicate and integrate with each other. Why reinvent functionality that isn't core to your application when it -already exists in another application?

        +already exists in another application?

        -

        This class covers some common ways you can send and receive content between +

        This class covers some common ways you can send and receive simple data between applications using {@link android.content.Intent} APIs and the {@link android.view.ActionProvider} object.

        Lessons

        - -
        -
        Sending Content to Other Apps
        + +
        +
        Sending Simple Data to Other Apps
        Learn how to set up your application to be able to send text and binary data to other -applications with intents.
        - -
        Receiving Content from Other Apps
        -
        Learn how to set up your application to receive text and binary data from intents.
        - -
        Adding an Easy Share Action
        -
        Learn how to add a "share" action item to your action bar.
        -
        +applications with intents. + +
        Receiving Simple Data from Other Apps
        +
        Learn how to set up your application to receive text and binary data from intents.
        + +
        Adding an Easy Share Action
        +
        Learn how to add a "share" action item to your action bar.
        +
        diff --git a/docs/html/training/sharing/receive.jd b/docs/html/training/sharing/receive.jd index 7ec3defc9c4b2ead85ba7b895fc243ef489484af..8c5f862164b59ee400edba2fce6d709a3ab77b63 100644 --- a/docs/html/training/sharing/receive.jd +++ b/docs/html/training/sharing/receive.jd @@ -1,5 +1,5 @@ -page.title=Receiving Content from Other Apps -parent.title=Sharing Content +page.title=Receiving Simple Data from Other Apps +parent.title=Sharing Simple Data parent.link=index.html trainingnavtop=true @@ -30,26 +30,26 @@ Intent Filters -

        Just as your application can send data to other applications, so too can it easily receive data -from applications. Think about how users interact with your application, and what data types you -want to receive from other applications. For example, a social networking application would likely -be interested in receiving text content, like an interesting web URL, from another app. The +

        Just as your application can send data to other applications, so too can it easily receive data +from applications. Think about how users interact with your application, and what data types you +want to receive from other applications. For example, a social networking application would likely +be interested in receiving text content, like an interesting web URL, from another app. The Google+ Android -application -accepts both text and single or multiple images. With this app, a user can easily start a +application +accepts both text and single or multiple images. With this app, a user can easily start a new Google+ post with photos from the Android Gallery app.

        Update Your Manifest

        -

        Intent filters inform the system what intents an application component is willing to accept. -Similar to how you constructed an intent with action {@link android.content.Intent#ACTION_SEND} in -the Send Content to Other Apps Using Intents -lesson, you create intent filters in order to be able to receive intents with this action. You -define an intent filter in your manifest, using the +

        Intent filters inform the system what intents an application component is willing to accept. +Similar to how you constructed an intent with action {@link android.content.Intent#ACTION_SEND} in +the Sending Simple Data to Other Apps +lesson, you create intent filters in order to be able to receive intents with this action. You +define an intent filter in your manifest, using the <intent-filter> -element. For example, if your application handles receiving text content, a single image of any +href="{@docRoot}guide/components/intents-filters.html#ifs"><intent-filter> +element. For example, if your application handles receiving text content, a single image of any type, or multiple images of any type, your manifest would look like:

        @@ -72,24 +72,24 @@ type, or multiple images of any type, your manifest would look like:

        </activity>
        -

        Note: For more information on intent filters and intent resolution +

        Note: For more information on intent filters and intent resolution please read Intents and Intent Filters

        When another application tries to share any of these things by constructing an intent and passing it to {@link android.content.Context#startActivity(android.content.Intent) startActivity()}, your -application will be listed as an option in the intent chooser. If the user selects your application, -the corresponding activity (.ui.MyActivity in the example above) will be started. It +application will be listed as an option in the intent chooser. If the user selects your application, +the corresponding activity (.ui.MyActivity in the example above) will be started. It is then up to you to handle the content appropriately within your code and UI.

        Handle the Incoming Content

        To handle the content delivered by an {@link android.content.Intent}, start by calling {@link -android.content.Intent#getIntent(String) getIntent()} -to get {@link android.content.Intent} object. Once you have the object, you can examine its -contents to determine what to do next. Keep in mind that if this activity can be started from other -parts of the system, such as the launcher, then you will need to take this into consideration when +android.content.Intent#getIntent(String) getIntent()} +to get {@link android.content.Intent} object. Once you have the object, you can examine its +contents to determine what to do next. Keep in mind that if this activity can be started from other +parts of the system, such as the launcher, then you will need to take this into consideration when examining the intent.

        @@ -143,7 +143,7 @@ know what some other application may send you. For example, the wrong MIME type
         image being sent might be extremely large. Also, remember to process binary data in a separate
         thread rather than the main ("UI") thread.

        -

        Updating the UI can be as simple as populating an {@link android.widget.EditText}, or it can -be more complicated like applying an interesting photo filter to an image. It's really specific +

        Updating the UI can be as simple as populating an {@link android.widget.EditText}, or it can +be more complicated like applying an interesting photo filter to an image. It's really specific to your application what happens next.

        diff --git a/docs/html/training/sharing/send.jd b/docs/html/training/sharing/send.jd index 9cb8eac6af103af0c3d3d393aa58757e94cbe407..f5da68feb9342eb6bbd4637424aefff75555fe9f 100644 --- a/docs/html/training/sharing/send.jd +++ b/docs/html/training/sharing/send.jd @@ -1,9 +1,9 @@ -page.title=Sending Content to Other Apps -parent.title=Sharing Content +page.title=Sending Simple Data to Other Apps +parent.title=Sharing Simple Data parent.link=index.html trainingnavtop=true -next.title=Receiving Content from Other Apps +next.title=Receiving Simple Data from Other Apps next.link=receive.html @jd:body @@ -29,22 +29,22 @@ Intent Filters -

        When you construct an intent, you must specify the action you want the intent to "trigger." -Android defines several actions, including {@link android.content.Intent#ACTION_SEND} which, as -you can probably guess, indicates that the intent is sending data from one activity to another, -even across process boundaries. To send data to another activity, all you need to do is specify -the data and its type, the system will identify compatible receiving activities and display them -to the user (if there are multiple options) or immediately start the activity (if there is only -one option). Similarly, you can advertise the data types that your activities support receiving +

        When you construct an intent, you must specify the action you want the intent to "trigger." +Android defines several actions, including {@link android.content.Intent#ACTION_SEND} which, as +you can probably guess, indicates that the intent is sending data from one activity to another, +even across process boundaries. To send data to another activity, all you need to do is specify +the data and its type, the system will identify compatible receiving activities and display them +to the user (if there are multiple options) or immediately start the activity (if there is only +one option). Similarly, you can advertise the data types that your activities support receiving from other applications by specifying them in your manifest.

        -

        Sending and receiving data between applications with intents is most commonly used for social -sharing of content. Intents allow users to share information quickly and easily, using their +

        Sending and receiving data between applications with intents is most commonly used for social +sharing of content. Intents allow users to share information quickly and easily, using their favorite applications.

        -

        Note: The best way to add a share action item to an -{@link android.app.ActionBar} is to use {@link android.widget.ShareActionProvider}, which became -available in API level 14. {@link android.widget.ShareActionProvider} is discussed in the lesson +

        Note: The best way to add a share action item to an +{@link android.app.ActionBar} is to use {@link android.widget.ShareActionProvider}, which became +available in API level 14. {@link android.widget.ShareActionProvider} is discussed in the lesson about Adding an Easy Share Action.

        @@ -58,10 +58,10 @@ on a handset.

        -

        The most straightforward and common use of the {@link android.content.Intent#ACTION_SEND} -action is sending text content from one activity to another. For example, the built-in Browser -app can share the URL of the currently-displayed page as text with any application. This is useful -for sharing an article or website with friends via email or social networking. Here is the code to +

        The most straightforward and common use of the {@link android.content.Intent#ACTION_SEND} +action is sending text content from one activity to another. For example, the built-in Browser +app can share the URL of the currently-displayed page as text with any application. This is useful +for sharing an article or website with friends via email or social networking. Here is the code to implement this type of sharing:

        @@ -72,12 +72,12 @@ sendIntent.setType("text/plain");
         startActivity(sendIntent);
         
        -

        If there's an installed application with a filter that matches -{@link android.content.Intent#ACTION_SEND} and MIME type text/plain, the Android system will run -it; if more than one application matches, the system displays a disambiguation dialog (a "chooser") -that allows the user to choose an app. If you call +

        If there's an installed application with a filter that matches +{@link android.content.Intent#ACTION_SEND} and MIME type text/plain, the Android system will run +it; if more than one application matches, the system displays a disambiguation dialog (a "chooser") +that allows the user to choose an app. If you call {@link android.content.Intent#createChooser(android.content.Intent, CharSequence) -Intent.createChooser()} +Intent.createChooser()} for the intent, Android will always display the chooser. This has some advantages:

        @@ -100,17 +100,17 @@ startActivity(Intent.createChooser(sendIntent, getResources().getText(R.

        The resulting dialog is shown in figure 1.

        -

        Optionally, you can set some standard extras for the intent: -{@link android.content.Intent#EXTRA_EMAIL}, {@link android.content.Intent#EXTRA_CC}, -{@link android.content.Intent#EXTRA_BCC}, {@link android.content.Intent#EXTRA_SUBJECT}. However, -if the receiving application is not designed to use them, nothing will happen. You can use -custom extras as well, but there's no effect unless the receiving application understands them. +

        Optionally, you can set some standard extras for the intent: +{@link android.content.Intent#EXTRA_EMAIL}, {@link android.content.Intent#EXTRA_CC}, +{@link android.content.Intent#EXTRA_BCC}, {@link android.content.Intent#EXTRA_SUBJECT}. However, +if the receiving application is not designed to use them, nothing will happen. You can use +custom extras as well, but there's no effect unless the receiving application understands them. Typically, you'd use custom extras defined by the receiving application itself.

        -

        Note: Some e-mail applications, such as Gmail, expect a -{@link java.lang.String String[]} for extras like {@link android.content.Intent#EXTRA_EMAIL} and -{@link android.content.Intent#EXTRA_CC}, use -{@link android.content.Intent#putExtra(String,String[]) putExtra(String, String[])} to add these +

        Note: Some e-mail applications, such as Gmail, expect a +{@link java.lang.String String[]} for extras like {@link android.content.Intent#EXTRA_EMAIL} and +{@link android.content.Intent#EXTRA_CC}, use +{@link android.content.Intent#putExtra(String,String[]) putExtra(String, String[])} to add these to your intent.

        @@ -134,34 +134,26 @@ startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.
      • You can use a MIME type of {@code "*/*"}, but this will only match activities that are able to handle generic data streams.
      • The receiving application needs permission to access the data the {@link android.net.Uri} -points to. There are a number of ways to handle this: +points to. The recommended ways to do this are:
          -
        • Write the data to a file on external/shared storage (such as the SD card), which all apps -can read. Use {@link android.net.Uri#fromFile(java.io.File) Uri.fromFile()} to create the -{@link android.net.Uri} that can be passed to the share intent. However, keep in mind that not -all applications process a {@code file://} style {@link android.net.Uri}.
        • -
        • Write the data to a file in your own application directory using {@link -android.content.Context#openFileOutput(java.lang.String, int) openFileOutput()} with mode {@link -android.content.Context#MODE_WORLD_READABLE} after which {@link -android.content.Context#getFileStreamPath(java.lang.String) getFileStreamPath()} can be used to -return a {@link java.io.File}. As with the previous option, {@link -android.net.Uri#fromFile(java.io.File) Uri.fromFile()} will create a {@code file://} style {@link -android.net.Uri} for your share intent.
        • -
        • Media files like images, videos and audio can be scanned and added to the system {@link -android.provider.MediaStore} using {@link +
        • Store the data in your own {@link android.content.ContentProvider}, making sure that other +apps have the correct permission to access your provider. The preferred mechanism for providing +access is to use per-URI permissions which are +temporary and only grant access to the receiving application. An easy way to create a +{@link android.content.ContentProvider} like this is to use the +{@link android.support.v4.content.FileProvider} helper class.
        • +
        • Use the system {@link android.provider.MediaStore}. The {@link android.provider.MediaStore} +is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API +level 11) it can also store non-media types (see +{@link android.provider.MediaStore.Files MediaStore.Files} for more info). Files can be inserted +into the {@link android.provider.MediaStore} using {@link android.media.MediaScannerConnection#scanFile(android.content.Context, java.lang.String[], -java.lang.String[], android.media.MediaScannerConnection.OnScanCompletedListener) scanFile()}. The -{@link -android.media.MediaScannerConnection.OnScanCompletedListener#onScanCompleted(java.lang.String, -android.net.Uri) onScanCompleted()} callback returns a {@code content://} style {@link -android.net.Uri} suitable for including in your share intent.
        • -
        • Images can be inserted into the system {@link android.provider.MediaStore} using {@link -android.provider.MediaStore.Images.Media#insertImage(android.content.ContentResolver, -android.graphics.Bitmap, java.lang.String, java.lang.String) insertImage()} which will return a -{@code content://} style {@link android.net.Uri} suitable for including in a share intent.
        • -
        • Store the data in your own {@link android.content.ContentProvider}, make sure that other -apps have the correct permission to access your provider (or use per-URI permissions).
        • +java.lang.String[], android.media.MediaScannerConnection.OnScanCompletedListener) scanFile()} after +which a {@code content://} style {@link android.net.Uri} suitable for sharing is passed to the +provided {@link android.media.MediaScannerConnection.OnScanCompletedListener#onScanCompleted( +java.lang.String, android.net.Uri) onScanCompleted()} callback. Note that once added to the system +{@link android.provider.MediaStore} the content is accessible to any app on the device.
      • diff --git a/docs/html/training/sharing/shareaction.jd b/docs/html/training/sharing/shareaction.jd index 873f61457b6909349753e1f961011ea2b6b4450f..ee811dabdc30e1d5cd259fdb03626f1e4f17f581 100644 --- a/docs/html/training/sharing/shareaction.jd +++ b/docs/html/training/sharing/shareaction.jd @@ -3,7 +3,7 @@ parent.title=Sharing Content parent.link=index.html trainingnavtop=true -previous.title=Receiving Content from Other Apps +previous.title=Receiving Simple Data from Other Apps previous.link=receive.html @jd:body @@ -28,7 +28,7 @@ previous.link=receive.html -

        Implementing an effective and user friendly share action in your {@link android.app.ActionBar} +

        Implementing an effective and user friendly share action in your {@link android.app.ActionBar} is made even easier with the introduction of {@link android.view.ActionProvider} in Android 4.0 (API Level 14). An {@link android.view.ActionProvider}, once attached to a menu item in the action bar, handles both the appearance and behavior of that item. In the case of {@link @@ -47,36 +47,48 @@ starting with API Level 14 and higher.

        Update Menu Declarations

        -

        To get started with {@link android.widget.ShareActionProvider ShareActionProviders}, define the android:actionProviderClass attribute for the corresponding <item> in your menu resource file:

        +

        + To get started with {@link android.widget.ShareActionProvider ShareActionProviders}, + define the android:actionProviderClass attribute for the corresponding + <item> in your menu resource file:

         <menu xmlns:android="http://schemas.android.com/apk/res/android">
        -    <item android:id="@+id/menu_item_share"
        -        android:showAsAction="ifRoom"
        -        android:title="Share"
        -        android:actionProviderClass="android.widget.ShareActionProvider" />
        +    <item
        +            android:id="@+id/menu_item_share"
        +            android:showAsAction="ifRoom"
        +            android:title="Share"
        +            android:actionProviderClass=
        +                "android.widget.ShareActionProvider" />
             ...
         </menu>
         
        -

        This delegates responsibility for the item's appearance and function to -{@link android.widget.ShareActionProvider}. However, you will need to tell the provider what you -would like to share.

        +

        + This delegates responsibility for the item's appearance and function to + {@link android.widget.ShareActionProvider}. However, you will need to tell the provider what you + would like to share. +

        Set the Share Intent

        -

        In order for {@link android.widget.ShareActionProvider} to function, you must provide it a share -intent. This share intent should be the same as described in the Sending Content to Other Apps -lesson, with action {@link android.content.Intent#ACTION_SEND} and additional data set via extras -like {@link android.content.Intent#EXTRA_TEXT} and {@link android.content.Intent#EXTRA_STREAM}. To -assign a share intent, first find the corresponding {@link android.view.MenuItem} while inflating -your menu resource in your {@link android.app.Activity} or {@link android.app.Fragment}. Next, call -{@link android.view.MenuItem#getActionProvider() MenuItem.getActionProvider()} to retreive an -instance of {@link android.widget.ShareActionProvider}. Use {@link -android.widget.ShareActionProvider#setShareIntent(android.content.Intent) setShareIntent()} to -update the share intent associated with that action item. Here's an example:

        +

        + In order for {@link android.widget.ShareActionProvider} to function, you must provide it a share + intent. This share intent should be the same as described in the + Sending Simple Data to Other Apps lesson, + with action {@link android.content.Intent#ACTION_SEND} and additional data set via extras + like {@link android.content.Intent#EXTRA_TEXT} and {@link android.content.Intent#EXTRA_STREAM}. + To assign a share intent, first find the corresponding {@link android.view.MenuItem} while + inflating your menu resource in your {@link android.app.Activity} or + {@link android.app.Fragment}. Next, call {@link android.view.MenuItem#getActionProvider + MenuItem.getActionProvider()} to retrieve an instance of + {@link android.widget.ShareActionProvider}. Use + {@link android.widget.ShareActionProvider#setShareIntent(android.content.Intent) + setShareIntent()} to update the share intent associated with that action item. Here's an + example: +

         private ShareActionProvider mShareActionProvider;
        @@ -105,8 +117,8 @@ private void setShareIntent(Intent shareIntent) {
         }
         
        -

        You may only need to set the share intent once during the creation of your menus, or you may -want to set it and then update it as the UI changes. For example, when you view photos full screen +

        You may only need to set the share intent once during the creation of your menus, or you may +want to set it and then update it as the UI changes. For example, when you view photos full screen in the Gallery app, the sharing intent changes as you flip between photos.

        For further discussion about the {@link android.widget.ShareActionProvider} object, see the These classes and articles provide information about how to +test your Android application.

        diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index c99fc96b0033bc3c1052c623a5b49052d5e9e9c7..39386bc1cc6674467674bc14018fbe9938c0e67d 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -68,27 +68,23 @@ include the action bar on devices running Android 2.1 or higher." - + - - - + @@ -405,7 +463,7 @@ include the action bar on devices running Android 2.1 or higher." Connecting Devices Wirelessly - + - +
      • + Testing Using Mock Locations + +
      • @@ -989,49 +1027,13 @@ include the action bar on devices running Android 2.1 or higher." + +
        IllegalStateException + IllegalStateException if this method was called at an inappropriate time, such as before the LocationServiceClient has bound to the remote service. @@ -1998,7 +2005,7 @@ From interface void requestActivityUpdates - (long detectionIntervalMillis, PendingIntent callbackIntent) + (long detectionIntervalMillis, PendingIntent callbackIntent)
        @@ -2067,7 +2074,7 @@ From interface
        Throws
        - - + @@ -803,7 +810,7 @@ Summary: - + @@ -907,7 +914,7 @@ android.os.Parcelable @@ -1146,7 +1153,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -1213,7 +1220,7 @@ From class final - Class<?> + Class<?> @@ -1434,7 +1441,7 @@ From interface public static final - String + String EXTRA_ACTIVITY_RESULT @@ -1523,7 +1530,7 @@ From interface ActivityRecognitionResult - (List<DetectedActivity> probableActivities, long time, long elapsedRealtimeMillis) + (List<DetectedActivity> probableActivities, long time, long elapsedRealtimeMillis)
        @@ -1664,7 +1671,7 @@ From interface ActivityRecognitionResult extractResult - (Intent intent) + (Intent intent)
        @@ -1792,7 +1799,7 @@ From interface - List<DetectedActivity> + List<DetectedActivity> getProbableActivities () @@ -1861,7 +1868,7 @@ From interface boolean hasResult - (Intent intent) + (Intent intent)
        @@ -1896,7 +1903,7 @@ From interface - String + String toString () @@ -1928,7 +1935,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
        diff --git a/docs/html/reference/com/google/android/gms/location/DetectedActivity.html b/docs/html/reference/com/google/android/gms/location/DetectedActivity.html index a2a6bacdfe41e26b5b3a99047b50239447d2c0af..f90cac5d3fa48dfa0e38d85d903169bfe442663e 100644 --- a/docs/html/reference/com/google/android/gms/location/DetectedActivity.html +++ b/docs/html/reference/com/google/android/gms/location/DetectedActivity.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + DetectedActivity | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - + @@ -1017,7 +1024,7 @@ android.os.Parcelable - String + String @@ -1064,7 +1071,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -1131,7 +1138,7 @@ From class final - Class<?> + Class<?> @@ -1794,7 +1801,7 @@ From interface - String + String toString() @@ -1826,7 +1833,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
        diff --git a/docs/html/reference/com/google/android/gms/location/Geofence.Builder.html b/docs/html/reference/com/google/android/gms/location/Geofence.Builder.html index 88e56ac076a8f131b1293c370364a092d9eee4c4..32709f849bfaa83231fbd31d13a9b9438a9fe0ec 100644 --- a/docs/html/reference/com/google/android/gms/location/Geofence.Builder.html +++ b/docs/html/reference/com/google/android/gms/location/Geofence.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Geofence.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -875,7 +882,7 @@ Summary: Geofence.Builder @@ -990,7 +997,7 @@ From class final - Class<?> + Class<?> @@ -940,7 +907,7 @@ From class final - Class<?> + Class<?> - - - - - - + - - - - - -
        IllegalStateException + IllegalStateException if this method was called at an inappropriate time, such as before the LocationServiceClient has bound to the remote service. diff --git a/docs/html/reference/com/google/android/gms/location/ActivityRecognitionResult.html b/docs/html/reference/com/google/android/gms/location/ActivityRecognitionResult.html index ecd5eaa86889415b67cd41be71e59430485fd397..a78f385b820dfebb5dc2984b422bb8ef0ed89f18 100644 --- a/docs/html/reference/com/google/android/gms/location/ActivityRecognitionResult.html +++ b/docs/html/reference/com/google/android/gms/location/ActivityRecognitionResult.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + ActivityRecognitionResult | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        StringString EXTRA_ACTIVITY_RESULT
        - ActivityRecognitionResult(List<DetectedActivity> probableActivities, long time, long elapsedRealtimeMillis) + ActivityRecognitionResult(List<DetectedActivity> probableActivities, long time, long elapsedRealtimeMillis)
        Constructs an ActivityRecognitionResult.
        @@ -972,7 +979,7 @@ android.os.Parcelable ActivityRecognitionResult
        - extractResult(Intent intent) + extractResult(Intent intent)
        Extracts the ActivityRecognitionResult from an Intent.
        @@ -1043,7 +1050,7 @@ android.os.Parcelable - List<DetectedActivity> + List<DetectedActivity>
        getProbableActivities() @@ -1084,7 +1091,7 @@ android.os.Parcelable boolean - hasResult(Intent intent) + hasResult(Intent intent)
        Returns true if an Intent contains an ActivityRecognitionResult.
        @@ -1099,7 +1106,7 @@ android.os.Parcelable - String + String
        toString() @@ -1118,7 +1125,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
        clone() @@ -1184,7 +1191,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1277,7 +1284,7 @@ From class - String + String toString() @@ -1348,7 +1355,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
        java.lang.Objectjava.lang.Object
        toString() @@ -1036,7 +1043,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
        clone() @@ -1102,7 +1109,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1195,7 +1202,7 @@ From class - String + String toString() @@ -1266,7 +1273,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
        java.lang.Objectjava.lang.Object
        - setRequestId(String requestId) + setRequestId(String requestId)
        Sets the request ID of the geofence.
        @@ -923,7 +930,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -961,7 +968,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1054,7 +1061,7 @@ From class - String + String toString() @@ -1222,7 +1229,7 @@ From class
        Throws
        - @@ -1339,7 +1346,7 @@ From class Geofence.BuildersetRequestId - (String requestId) + (String requestId)
        diff --git a/docs/html/reference/com/google/android/gms/location/Geofence.html b/docs/html/reference/com/google/android/gms/location/Geofence.html index 00d8e4b0f371be6b9bd81d3aaa7fc867ff446241..dc85cbf15b9963751aa7f97bfe2841f871a72ff7 100644 --- a/docs/html/reference/com/google/android/gms/location/Geofence.html +++ b/docs/html/reference/com/google/android/gms/location/Geofence.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Geofence | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -835,7 +842,7 @@ Summary: - + @@ -843,7 +850,7 @@ Summary: - + @@ -880,7 +887,7 @@ Summary: @@ -1446,7 +1453,7 @@ From class final - Class<?> + Class<?> - + @@ -791,7 +798,7 @@ Summary: @@ -1144,7 +1151,7 @@ From class final - Class<?> + Class<?> - + @@ -799,14 +809,14 @@ Summary: - + - + @@ -814,7 +824,7 @@ Summary: - + @@ -822,70 +832,70 @@ Summary: - + - + - + - + - + - + - + - + - + - + @@ -954,7 +964,7 @@ Summary: Person @@ -1069,7 +1106,7 @@ From class final - Class<?> + Class<?> - + @@ -855,7 +862,7 @@ Summary: ItemScope.Builder @@ -1890,7 +1897,7 @@ From class final - Class<?> + Class<?> - + @@ -837,7 +844,7 @@ Summary: Moment.Builder @@ -1006,7 +1013,7 @@ From class final - Class<?> + Class<?> - + @@ -952,7 +959,7 @@ From class - Iterator<T> + Iterator<T> @@ -1042,7 +1049,7 @@ From class final - Class<?> + Class<?> - + @@ -812,7 +819,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -879,7 +886,7 @@ From class final - Class<?> + Class<?> - + @@ -826,7 +833,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -893,7 +900,7 @@ From class final - Class<?> + Class<?> - + @@ -826,7 +833,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -893,7 +900,7 @@ From class final - Class<?> + Class<?> - + @@ -819,7 +826,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -886,7 +893,7 @@ From class final - Class<?> + Class<?> - + @@ -819,7 +826,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -886,7 +893,7 @@ From class final - Class<?> + Class<?> @@ -854,7 +861,7 @@ Summary: - String + String @@ -908,7 +915,7 @@ Summary: - String + String @@ -1245,7 +1252,7 @@ From interface abstract - String + String getDepartment() @@ -1275,7 +1282,7 @@ From interface abstract - String + String getDescription() @@ -1305,7 +1312,7 @@ From interface abstract - String + String getEndDate() @@ -1318,7 +1325,7 @@ From interface
        -

        The date the person left this organization. +

        The date that the person left this organization.

        @@ -1335,7 +1342,7 @@ From interface abstract - String + String getLocation () @@ -1365,7 +1372,7 @@ From interface abstract - String + String getName () @@ -1395,7 +1402,7 @@ From interface abstract - String + String getStartDate () @@ -1408,7 +1415,7 @@ From interface
        -

        The date the person joined this organization. +

        The date that the person joined this organization.

        @@ -1425,7 +1432,7 @@ From interface abstract - String + String getTitle () @@ -1468,7 +1475,8 @@ From interface
        -

        The type of organization. Possible values are: +

        The type of organization. Possible values include, but are not limited to, the following + values: - "work" - Work. - "school" - School.

        @@ -1770,8 +1778,8 @@ From interface
        -

        If "true", indicates this organization is the person's primary one (typically interpreted as - current one). +

        If "true", indicates this organization is the person's primary one, which is typically + interpreted as the current one.

        diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.PlacesLived.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.PlacesLived.html index 77784348c6301ead86186f75a775f0edf370a811..bfcd8a1d3ce044fa3488b8004e0c28e81aaf9d91 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.PlacesLived.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.PlacesLived.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.PlacesLived | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - + @@ -868,7 +875,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -935,7 +942,7 @@ From class final - Class<?> + Class<?> - + @@ -769,32 +776,12 @@ Summary: - - - - - - - - - - - - - - @@ -810,32 +797,12 @@ Summary: - - - - - - - - - - - - - -
        IllegalArgumentException + IllegalArgumentException if any parameters are not set or out of range getRequestId() @@ -992,7 +999,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - String + String getRequestId () diff --git a/docs/html/reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html b/docs/html/reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html index dfd088c8f911af8906d88ef780b2833a5f7bc45f..91f99306235d84766ecedbc7fd2bc5127ad92e70 100644 --- a/docs/html/reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html +++ b/docs/html/reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationClient.OnAddGeofencesResultListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onAddGeofencesResult(int statusCode, String[] geofenceRequestIds) + onAddGeofencesResult(int statusCode, String[] geofenceRequestIds)
        Called when the addGeofences(List, PendingIntent, OnAddGeofencesResultListener) operation completes successfully or unsuccessfully.
        @@ -821,7 +828,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onAddGeofencesResult - (int statusCode, String[] geofenceRequestIds) + (int statusCode, String[] geofenceRequestIds)
        diff --git a/docs/html/reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html b/docs/html/reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html index c273503cf0d4a028c878569d03155808f238379f..542e3c1aa75db2c9eb4c32f4baa51f63747d64e5 100644 --- a/docs/html/reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html +++ b/docs/html/reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationClient.OnRemoveGeofencesResultListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onRemoveGeofencesByPendingIntentResult(int statusCode, PendingIntent pendingIntent) + onRemoveGeofencesByPendingIntentResult(int statusCode, PendingIntent pendingIntent)
        Called when the removeGeofences(PendingIntent, OnRemoveGeofencesResultListener) operation completes successfully or unsuccessfully.
        @@ -771,7 +778,7 @@ onkeyup="return search_changed(event, false, '/')" /> void
        - onRemoveGeofencesByRequestIdsResult(int statusCode, String[] geofenceRequestIds) + onRemoveGeofencesByRequestIdsResult(int statusCode, String[] geofenceRequestIds)
        Called when the removeGeofences(List, OnRemoveGeofencesResultListener) operation completes successfully or unsuccessfully.
        @@ -840,7 +847,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onRemoveGeofencesByPendingIntentResult - (int statusCode, PendingIntent pendingIntent) + (int statusCode, PendingIntent pendingIntent)
        @@ -889,7 +896,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onRemoveGeofencesByRequestIdsResult - (int statusCode, String[] geofenceRequestIds) + (int statusCode, String[] geofenceRequestIds)
        diff --git a/docs/html/reference/com/google/android/gms/location/LocationClient.html b/docs/html/reference/com/google/android/gms/location/LocationClient.html index 6bdbc941ac85e75aa511c2688dc0179b585de98d..efd157e56280bd9872270d348df94d92a774596d 100644 --- a/docs/html/reference/com/google/android/gms/location/LocationClient.html +++ b/docs/html/reference/com/google/android/gms/location/LocationClient.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationClient | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        StringString KEY_LOCATION_CHANGED Key used for a Bundle extra holding a Location value when a location change is broadcast using a PendingIntent.
        StringString KEY_MOCK_LOCATION Key used for the Bundle extra in Location object holding a boolean indicating whether the location was set using setMockLocation(Location). - LocationClient(Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + LocationClient(Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener)
        Creates a LocationClient.
        @@ -911,7 +918,7 @@ Summary: void
        - addGeofences(List<Geofence> geofences, PendingIntent pendingIntent, LocationClient.OnAddGeofencesResultListener listener) + addGeofences(List<Geofence> geofences, PendingIntent pendingIntent, LocationClient.OnAddGeofencesResultListener listener)
        Sets alerts to be notified when the device enters or exits one of the specified geofences.
        @@ -966,7 +973,7 @@ Summary: int
        - getErrorCode(Intent intent) + getErrorCode(Intent intent)
        Returns the error code that explains the error that triggered this intent.
        @@ -985,7 +992,7 @@ Summary: int
        - getGeofenceTransition(Intent intent) + getGeofenceTransition(Intent intent)
        Returns the transition type of geofence transition alert.
        @@ -1000,7 +1007,7 @@ Summary: - Location + Location
        getLastLocation() @@ -1018,10 +1025,10 @@ Summary: static - List<Geofence> + List<Geofence> - getTriggeringGeofences(Intent intent) + getTriggeringGeofences(Intent intent)
        Returns a list of geofences that triggers this geofence transition alert.
        @@ -1039,7 +1046,7 @@ Summary: boolean
        - hasError(Intent intent) + hasError(Intent intent)
        Whether an error triggered this intent.
        @@ -1169,7 +1176,7 @@ Summary: void
        - removeGeofences(List<String> geofenceRequestIds, LocationClient.OnRemoveGeofencesResultListener listener) + removeGeofences(List<String> geofenceRequestIds, LocationClient.OnRemoveGeofencesResultListener listener)
        Removes geofences by their request IDs.
        @@ -1187,7 +1194,7 @@ Summary: void
        - removeGeofences(PendingIntent pendingIntent, LocationClient.OnRemoveGeofencesResultListener listener) + removeGeofences(PendingIntent pendingIntent, LocationClient.OnRemoveGeofencesResultListener listener)
        Removes all geofences associated with the given pendingIntent.
        @@ -1223,7 +1230,7 @@ Summary: void
        - removeLocationUpdates(PendingIntent callbackIntent) + removeLocationUpdates(PendingIntent callbackIntent)
        Removes all location updates for the given pending intent.
        @@ -1241,7 +1248,7 @@ Summary: void
        - requestLocationUpdates(LocationRequest request, PendingIntent callbackIntent) + requestLocationUpdates(LocationRequest request, PendingIntent callbackIntent)
        Requests location updates with a callback on the specified PendingIntent.
        @@ -1277,7 +1284,7 @@ Summary: void
        - requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper) + requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper)
        Requests location updates with a callback on the specified Looper thread.
        @@ -1295,7 +1302,7 @@ Summary: void
        - setMockLocation(Location mockLocation) + setMockLocation(Location mockLocation)
        Sets the mock location to be used for the location provider.
        @@ -1379,7 +1386,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1417,7 +1424,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1510,7 +1517,7 @@ From class - String + String toString() @@ -1819,7 +1826,7 @@ From interface public static final - String + String KEY_LOCATION_CHANGED @@ -1858,7 +1865,7 @@ From interface public static final - String + String KEY_MOCK_LOCATION @@ -1915,7 +1922,7 @@ From interface LocationClient - (Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + (Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener)
        @@ -1978,7 +1985,7 @@ From interface void addGeofences - (List<Geofence> geofences, PendingIntent pendingIntent, LocationClient.OnAddGeofencesResultListener listener) + (List<Geofence> geofences, PendingIntent pendingIntent, LocationClient.OnAddGeofencesResultListener listener)
        @@ -2040,23 +2047,23 @@ From interface
        Throws
        - - - - @@ -2144,7 +2151,7 @@ From interface int getErrorCode - (Intent intent) + (Intent intent)
        @@ -2189,7 +2196,7 @@ From interface int getGeofenceTransition - (Intent intent) + (Intent intent)
        @@ -2231,7 +2238,7 @@ From interface - Location + Location getLastLocation () @@ -2268,10 +2275,10 @@ From interface - List<Geofence> + List<Geofence> getTriggeringGeofences - (Intent intent) + (Intent intent)
        @@ -2316,7 +2323,7 @@ From interface boolean hasError - (Intent intent) + (Intent intent)
        @@ -2622,7 +2629,7 @@ From interface void removeGeofences - (List<String> geofenceRequestIds, LocationClient.OnRemoveGeofencesResultListener listener) + (List<String> geofenceRequestIds, LocationClient.OnRemoveGeofencesResultListener listener)
        @@ -2660,23 +2667,23 @@ From interface
        Throws
        SecurityException + SecurityException if the app does not have ACCESS_FINE_LOCATION permission
        IllegalStateException + IllegalStateException if the connection to Google Play Store Services hasn't been established
        IllegalArgumentException + IllegalArgumentException if geofences is null or empty
        NullPointerException + NullPointerException if intent or listener is null
        - - - - @@ -2700,7 +2707,7 @@ From interface void removeGeofences - (PendingIntent pendingIntent, LocationClient.OnRemoveGeofencesResultListener listener) + (PendingIntent pendingIntent, LocationClient.OnRemoveGeofencesResultListener listener)
        @@ -2743,18 +2750,18 @@ From interface
        Throws
        IllegalArgumentException + IllegalArgumentException if geofenceRequestIds is null or empty
        SecurityException + SecurityException if the app does not have ACCESS_FINE_LOCATION permission
        IllegalStateException + IllegalStateException if the connection to Google Play Store Services hasn't been established
        NullPointerException + NullPointerException if listener is null
        - - - @@ -2818,7 +2825,7 @@ From interface void removeLocationUpdates - (PendingIntent callbackIntent) + (PendingIntent callbackIntent)
        @@ -2857,7 +2864,7 @@ From interface void requestLocationUpdates - (LocationRequest request, PendingIntent callbackIntent) + (LocationRequest request, PendingIntent callbackIntent)
        @@ -2967,7 +2974,7 @@ From interface void requestLocationUpdates - (LocationRequest request, LocationListener listener, Looper looper) + (LocationRequest request, LocationListener listener, Looper looper)
        @@ -3026,7 +3033,7 @@ From interface void setMockLocation - (Location mockLocation) + (Location mockLocation)
        @@ -3057,7 +3064,7 @@ From interface
        Throws
        SecurityException + SecurityException if the app does not have ACCESS_FINE_LOCATION permission
        IllegalStateException + IllegalStateException if the connection to Google Play Store Services hasn't been established
        NullPointerException + NullPointerException if intent or listener is null
        - - + @@ -842,7 +849,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -909,7 +916,7 @@ From class final - Class<?> + Class<?> - + @@ -808,7 +815,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -875,7 +882,7 @@ From class final - Class<?> + Class<?> - + @@ -932,7 +939,7 @@ Summary: CameraUpdate @@ -1103,7 +1110,7 @@ From class final - Class<?> + Class<?> - + @@ -1256,7 +1263,7 @@ Summary: final - Location + Location + + + + + + + + + @@ -1787,7 +1812,7 @@ From class final - Class<?> + Class<?> - + @@ -953,7 +960,7 @@ android.os.Parcelable GoogleMapOptions @@ -1321,7 +1328,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -1388,7 +1395,7 @@ From class final - Class<?> + Class<?> @@ -1761,7 +1768,7 @@ From interface GoogleMapOptionscreateFromAttributes - (Context context, AttributeSet attrs) + (Context context, AttributeSet attrs)
        @@ -1850,7 +1857,7 @@ From interface - Boolean + Boolean getCompassEnabled () @@ -1916,7 +1923,7 @@ From interface - Boolean + Boolean getRotateGesturesEnabled () @@ -1949,7 +1956,7 @@ From interface - Boolean + Boolean getScrollGesturesEnabled () @@ -1982,7 +1989,7 @@ From interface - Boolean + Boolean getTiltGesturesEnabled () @@ -2015,7 +2022,7 @@ From interface - Boolean + Boolean getUseViewLifecycleInFragment () @@ -2048,7 +2055,7 @@ From interface - Boolean + Boolean getZOrderOnTop () @@ -2081,7 +2088,7 @@ From interface - Boolean + Boolean getZoomControlsEnabled () @@ -2114,7 +2121,7 @@ From interface - Boolean + Boolean getZoomGesturesEnabled () @@ -2318,7 +2325,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
        diff --git a/docs/html/reference/com/google/android/gms/maps/LocationSource.OnLocationChangedListener.html b/docs/html/reference/com/google/android/gms/maps/LocationSource.OnLocationChangedListener.html index 94a6ae1e18db6c8474e483cd92b021a79beb40f6..0dcad24fa9c977c892458b35b04c1cca41780eb6 100644 --- a/docs/html/reference/com/google/android/gms/maps/LocationSource.OnLocationChangedListener.html +++ b/docs/html/reference/com/google/android/gms/maps/LocationSource.OnLocationChangedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationSource.OnLocationChangedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - -
        SecurityException + SecurityException if the ACCESS_MOCK_LOCATION permission is not present or the Settings.Secure.ALLOW_MOCK_LOCATION system setting is not enabled. @@ -3119,7 +3126,7 @@ From interface
        Throws
        - - + @@ -1001,7 +1008,7 @@ android.os.Parcelable boolean @@ -1268,7 +1275,7 @@ android.os.Parcelable - String + String @@ -1315,7 +1322,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -1382,7 +1389,7 @@ From class final - Class<?> + Class<?> @@ -1897,7 +1904,7 @@ From interface boolean equals - (Object object) + (Object object)
        @@ -2319,7 +2326,7 @@ From interface
        Throws
        SecurityException + SecurityException if the ACCESS_MOCK_LOCATION permission is not present or the Settings.Secure.ALLOW_MOCK_LOCATION system setting is not enabled. diff --git a/docs/html/reference/com/google/android/gms/location/LocationListener.html b/docs/html/reference/com/google/android/gms/location/LocationListener.html index 48e3487d574587b69e9f91f1bf057a151b9ae291..da51ab75488233be14a5defec601fbb9678d2caa 100644 --- a/docs/html/reference/com/google/android/gms/location/LocationListener.html +++ b/docs/html/reference/com/google/android/gms/location/LocationListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onLocationChanged(Location location) + onLocationChanged(Location location)
        Called when the location has changed.
        @@ -823,7 +830,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onLocationChanged - (Location location) + (Location location)
        diff --git a/docs/html/reference/com/google/android/gms/location/LocationRequest.html b/docs/html/reference/com/google/android/gms/location/LocationRequest.html index c5d4a7b235d0105f1a144b5ff55f49fc667566cd..dcfc4d6f9803725db2bc58932c06c63696e978e2 100644 --- a/docs/html/reference/com/google/android/gms/location/LocationRequest.html +++ b/docs/html/reference/com/google/android/gms/location/LocationRequest.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationRequest | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - equals(Object object) + equals(Object object)
        toString() @@ -1287,7 +1294,7 @@ android.os.Parcelable void - writeToParcel(Parcel parcel, int flags) + writeToParcel(Parcel parcel, int flags)
        clone() @@ -1353,7 +1360,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1446,7 +1453,7 @@ From class - String + String toString() @@ -1517,7 +1524,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
        -
        IllegalArgumentException + IllegalArgumentException if the interval is less than zero
        @@ -2390,7 +2397,7 @@ From interface
        Throws
        -
        IllegalArgumentException + IllegalArgumentException if the interval is less than zero
        @@ -2447,7 +2454,7 @@ From interface
        Throws
        -
        IllegalArgumentException + IllegalArgumentException if numUpdates is 0 or less
        @@ -2511,7 +2518,7 @@ From interface
        Throws
        -
        IllegalArgumentException + IllegalArgumentException if the quality constant is not valid
        @@ -2566,7 +2573,7 @@ From interface
        Throws
        -
        IllegalArgumentException + IllegalArgumentException if smallestDisplacementMeters is negative
        @@ -2586,7 +2593,7 @@ From interface - String + String toString () @@ -2618,7 +2625,7 @@ From interface void writeToParcel - (Parcel parcel, int flags) + (Parcel parcel, int flags)
        diff --git a/docs/html/reference/com/google/android/gms/location/LocationStatusCodes.html b/docs/html/reference/com/google/android/gms/location/LocationStatusCodes.html index b53f7169cb81cf988b75f16292a3c78e1ec07668..f76ea0548cfff3c180f4733456d9d4c779582be9 100644 --- a/docs/html/reference/com/google/android/gms/location/LocationStatusCodes.html +++ b/docs/html/reference/com/google/android/gms/location/LocationStatusCodes.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationStatusCodes | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -880,7 +887,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -973,7 +980,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/location/package-summary.html b/docs/html/reference/com/google/android/gms/location/package-summary.html index c21638c799c8ad5553be0ca0f4c400e9f6f41823..bfd353e93ad0db600e9bf2babe961659d6fe8d31 100644 --- a/docs/html/reference/com/google/android/gms/location/package-summary.html +++ b/docs/html/reference/com/google/android/gms/location/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + com.google.android.gms.location | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -846,7 +853,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -939,7 +946,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/maps/CameraUpdateFactory.html b/docs/html/reference/com/google/android/gms/maps/CameraUpdateFactory.html index 3d2d2232ed3cadc90fe92f8b14197725072f5290..0f0da8f265b0d0b3a622daea29ecc9ab19ac3f23 100644 --- a/docs/html/reference/com/google/android/gms/maps/CameraUpdateFactory.html +++ b/docs/html/reference/com/google/android/gms/maps/CameraUpdateFactory.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + CameraUpdateFactory | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - zoomBy(float amount, Point focus) + zoomBy(float amount, Point focus)
        Returns a CameraUpdate that shifts the zoom level of the current camera viewpoint.
        @@ -1036,7 +1043,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1074,7 +1081,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1167,7 +1174,7 @@ From class - String + String toString() @@ -1601,7 +1608,7 @@ From class CameraUpdate zoomBy - (float amount, Point focus) + (float amount, Point focus)
        diff --git a/docs/html/reference/com/google/android/gms/maps/GoogleMap.CancelableCallback.html b/docs/html/reference/com/google/android/gms/maps/GoogleMap.CancelableCallback.html index 9af9d293854a153eac8bc18e3ae47f063eeded8c..56be2167249b25de81687de7823587dbe0f828cb 100644 --- a/docs/html/reference/com/google/android/gms/maps/GoogleMap.CancelableCallback.html +++ b/docs/html/reference/com/google/android/gms/maps/GoogleMap.CancelableCallback.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + GoogleMap.CancelableCallback | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getInfoContents(Marker marker) @@ -786,7 +793,7 @@ onkeyup="return search_changed(event, false, '/')" /> - View + View getInfoWindow(Marker marker) @@ -854,7 +861,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - View + View getInfoContents (Marker marker) @@ -903,7 +910,7 @@ onkeyup="return search_changed(event, false, '/')" /> abstract - View + View getInfoWindow (Marker marker) diff --git a/docs/html/reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.html b/docs/html/reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.html index 3ea198821f242ccdf89b503c797507d5db377c6b..096a1954b6ed825d4373da034a04354b145582ba 100644 --- a/docs/html/reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.html +++ b/docs/html/reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + GoogleMap.OnCameraChangeListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onMyLocationChange(Location location) + onMyLocationChange(Location location)
        Called when the Location of the My Location dot has changed (be it latitude/longitude, bearing or accuracy).
        @@ -830,7 +837,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onMyLocationChange - (Location location) + (Location location)
        diff --git a/docs/html/reference/com/google/android/gms/maps/GoogleMap.SnapshotReadyCallback.html b/docs/html/reference/com/google/android/gms/maps/GoogleMap.SnapshotReadyCallback.html index 12e63d68b7a52e87fc37ba351abdb0e1f517ee1d..ea31d25f230bf9d144c9638d60b9066d74dda867 100644 --- a/docs/html/reference/com/google/android/gms/maps/GoogleMap.SnapshotReadyCallback.html +++ b/docs/html/reference/com/google/android/gms/maps/GoogleMap.SnapshotReadyCallback.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + GoogleMap.SnapshotReadyCallback | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        - onSnapshotReady(Bitmap snapshot) + onSnapshotReady(Bitmap snapshot)
        Invoked when the snapshot has been taken.
        @@ -819,7 +826,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onSnapshotReady - (Bitmap snapshot) + (Bitmap snapshot)
        diff --git a/docs/html/reference/com/google/android/gms/maps/GoogleMap.html b/docs/html/reference/com/google/android/gms/maps/GoogleMap.html index ebb59c30c130f5c8f785be0731a56b0a137ad307..dc3e8744478150f685c364edbac375cdbe4f1ec0 100644 --- a/docs/html/reference/com/google/android/gms/maps/GoogleMap.html +++ b/docs/html/reference/com/google/android/gms/maps/GoogleMap.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + GoogleMap | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        getMyLocation() @@ -1633,6 +1640,24 @@ Summary: final + void + + setPadding(int left, int top, int right, int bottom) + +
        Sets padding on the map.
        + +
        + + + final + + void @@ -1644,7 +1669,7 @@ Summary: -
        @@ -1654,7 +1679,7 @@ Summary: void - snapshot(GoogleMap.SnapshotReadyCallback callback, Bitmap bitmap) + snapshot(GoogleMap.SnapshotReadyCallback callback, Bitmap bitmap)
        Takes a snapshot of the map.
        @@ -1662,7 +1687,7 @@ Summary: -
        @@ -1680,7 +1705,7 @@ Summary: -
        @@ -1720,7 +1745,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1758,7 +1783,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1851,7 +1876,7 @@ From class - String + String toString() @@ -2237,7 +2262,7 @@ From class
        Throws
        - @@ -2429,7 +2454,7 @@ From class
        Throws
        IllegalArgumentException + IllegalArgumentException if either the image or the position is unspecified in the options.
        - @@ -2769,7 +2794,7 @@ From class final - Location + Location getMyLocation() @@ -2802,7 +2827,7 @@ From class
        Throws
        IllegalArgumentException + IllegalArgumentException if the TileProvider is unspecified in the options.
        -
        IllegalStateException + IllegalStateException if the my-location layer is not enabled.
        @@ -3581,6 +3606,63 @@ From class + + +
        +

        + + public + + final + + + void + + setPadding + (int left, int top, int right, int bottom) +

        +
        +
        + + + +
        +
        + +

        Sets padding on the map. + +

        This method allows you to define a visible region on the map, to signal to the map that + portions of the map around the edges may be obscured, by setting padding on each of the four + edges of the map. Map functions will be adapted to the padding. For example, the zoom + controls, compass, copyright notices and Google logo will be moved to fit inside the defined + region, camera movements will be relative to the center of the visible region, etc.

        +
        +
        Parameters
        + + + + + + + + + + + + + +
        left + the number of pixels of padding to be added on the left of the map.
        top + the number of pixels of padding to be added on the top of the map.
        right + the number of pixels of padding to be added on the right of the map.
        bottom + the number of pixels of padding to be added on the bottom of the map. +
        +
        + +
        +
        + +
        @@ -3623,7 +3705,7 @@ From class void snapshot - (GoogleMap.SnapshotReadyCallback callback, Bitmap bitmap) + (GoogleMap.SnapshotReadyCallback callback, Bitmap bitmap)
        @@ -3634,11 +3716,11 @@ From class

        Takes a snapshot of the map. -

        +

        This method is equivalent to snapshot(SnapshotReadyCallback) but lets you provide a preallocated Bitmap. If the bitmap does not match the current dimensions of the map, another bitmap will be allocated that fits the map's dimensions. -

        +

        Although in most cases the object passed by the callback method is the same as the one given in parameter to this method, in some cases the returned object can be different (e.g. if the view's dimensions have changed by the time the snapshot is actually taken). Thus, you diff --git a/docs/html/reference/com/google/android/gms/maps/GoogleMapOptions.html b/docs/html/reference/com/google/android/gms/maps/GoogleMapOptions.html index fa78bc6cbd626a4cfcdc001c078573805f3a904c..ba0cc9a8c6fa2b6d35c8c0e615037fe26c0399ed 100644 --- a/docs/html/reference/com/google/android/gms/maps/GoogleMapOptions.html +++ b/docs/html/reference/com/google/android/gms/maps/GoogleMapOptions.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + GoogleMapOptions | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />

      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - createFromAttributes(Context context, AttributeSet attrs) + createFromAttributes(Context context, AttributeSet attrs)
        Creates a GoogleMapsOptions from the attribute set
        @@ -1001,7 +1008,7 @@ android.os.Parcelable - Boolean + Boolean
        getCompassEnabled() @@ -1033,7 +1040,7 @@ android.os.Parcelable - Boolean + Boolean getRotateGesturesEnabled() @@ -1049,7 +1056,7 @@ android.os.Parcelable - Boolean + Boolean getScrollGesturesEnabled() @@ -1065,7 +1072,7 @@ android.os.Parcelable - Boolean + Boolean getTiltGesturesEnabled() @@ -1081,7 +1088,7 @@ android.os.Parcelable - Boolean + Boolean getUseViewLifecycleInFragment() @@ -1097,7 +1104,7 @@ android.os.Parcelable - Boolean + Boolean getZOrderOnTop() @@ -1113,7 +1120,7 @@ android.os.Parcelable - Boolean + Boolean getZoomControlsEnabled() @@ -1129,7 +1136,7 @@ android.os.Parcelable - Boolean + Boolean getZoomGesturesEnabled() @@ -1239,7 +1246,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
        clone() @@ -1359,7 +1366,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1452,7 +1459,7 @@ From class - String + String toString() @@ -1523,7 +1530,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
        - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
        - onLocationChanged(Location location) + onLocationChanged(Location location)
        Called when a new user location is known.
        @@ -819,7 +826,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onLocationChanged - (Location location) + (Location location)
        diff --git a/docs/html/reference/com/google/android/gms/maps/LocationSource.html b/docs/html/reference/com/google/android/gms/maps/LocationSource.html index afc5cc4d374fdfe6e8a7ca68b96ddbcf2f95f715..9ba64e0721d93e8f57ffdd47a1760ff00a9b8ed5 100644 --- a/docs/html/reference/com/google/android/gms/maps/LocationSource.html +++ b/docs/html/reference/com/google/android/gms/maps/LocationSource.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LocationSource | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          +
        • + Overview +
        • Getting Started
        • -
        • - Architectural Overview +
        • + Implementing GCM Client
        • -
        • - Cloud Connection Server +
        • User Notifications
        • -
        • - GCM Client -
        • -
        • - GCM Server -
        • Advanced Topics
        • @@ -895,11 +902,11 @@ onkeyup="return search_changed(event, false, '/')" />
          Throws
          - - @@ -939,7 +946,7 @@ onkeyup="return search_changed(event, false, '/')" />
          Throws
          IllegalStateException + IllegalStateException if this provider is already active
          IllegalArgumentException + IllegalArgumentException if listener is null
          - diff --git a/docs/html/reference/com/google/android/gms/maps/MapFragment.html b/docs/html/reference/com/google/android/gms/maps/MapFragment.html index dbff4ab7f8bf27868406fee6ffc1b604874c1f0f..ecd7f78628b4ff8b2997e35e693e9cc38255d235 100644 --- a/docs/html/reference/com/google/android/gms/maps/MapFragment.html +++ b/docs/html/reference/com/google/android/gms/maps/MapFragment.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +MapFragment | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -1045,7 +1052,7 @@ android.content.ComponentCallbacks2 void @@ -1061,7 +1068,7 @@ android.content.ComponentCallbacks2 void @@ -1077,7 +1084,7 @@ android.content.ComponentCallbacks2 void @@ -1090,10 +1097,10 @@ android.content.ComponentCallbacks2 - View + View @@ -1141,7 +1148,7 @@ android.content.ComponentCallbacks2 void @@ -1223,7 +1230,7 @@ android.content.ComponentCallbacks2 void @@ -1273,7 +1280,7 @@ From class void @@ -1289,7 +1296,7 @@ From class boolean @@ -1302,7 +1309,7 @@ From class final - Activity + Activity @@ -1446,7 +1453,7 @@ From class final - String + String @@ -1577,7 +1584,7 @@ From class Fragment @@ -1705,7 +1712,7 @@ From class void @@ -1721,7 +1728,7 @@ From class void @@ -1737,7 +1744,7 @@ From class void @@ -1753,7 +1760,7 @@ From class void @@ -1769,7 +1776,7 @@ From class boolean @@ -1785,7 +1792,7 @@ From class void @@ -1817,7 +1824,7 @@ From class void @@ -1833,7 +1840,7 @@ From class void @@ -1846,10 +1853,10 @@ From class - View + View @@ -1945,7 +1952,7 @@ From class void @@ -1961,7 +1968,7 @@ From class void @@ -1993,7 +2000,7 @@ From class boolean @@ -2009,7 +2016,7 @@ From class void @@ -2041,7 +2048,7 @@ From class void @@ -2073,7 +2080,7 @@ From class void @@ -2137,7 +2144,7 @@ From class void @@ -2153,7 +2160,7 @@ From class void @@ -2169,7 +2176,7 @@ From class void @@ -2281,7 +2288,7 @@ From class void @@ -2297,7 +2304,7 @@ From class void @@ -2313,7 +2320,7 @@ From class void @@ -2329,7 +2336,7 @@ From class void @@ -2342,7 +2349,7 @@ From class - String + String @@ -2381,7 +2388,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -2448,7 +2455,7 @@ From class final - Class<?> + Class<?> @@ -2683,7 +2690,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.View.OnCreateContextMenuListener + android.view.View.OnCreateContextMenuListener
          @@ -2908,7 +2915,7 @@ From interface void onActivityCreated - (Bundle savedInstanceState) + (Bundle savedInstanceState)
          @@ -2937,7 +2944,7 @@ From interface void onAttach - (Activity activity) + (Activity activity)
          @@ -2966,7 +2973,7 @@ From interface void onCreate - (Bundle savedInstanceState) + (Bundle savedInstanceState)
          @@ -2992,10 +2999,10 @@ From interface - View + View onCreateView - (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
          @@ -3082,7 +3089,7 @@ From interface void onInflate - (Activity activity, AttributeSet attrs, Bundle savedInstanceState) + (Activity activity, AttributeSet attrs, Bundle savedInstanceState)
          @@ -3199,7 +3206,7 @@ From interface void onSaveInstanceState - (Bundle outState) + (Bundle outState)
          @@ -3228,7 +3235,7 @@ From interface void setArguments - (Bundle args) + (Bundle args)
          diff --git a/docs/html/reference/com/google/android/gms/maps/MapView.html b/docs/html/reference/com/google/android/gms/maps/MapView.html index 11bf3f6e1d48d33d7bfda506470a4044a89c5e66..a96cfbfb399a5e8548acb7eb174654791c03e3bb 100644 --- a/docs/html/reference/com/google/android/gms/maps/MapView.html +++ b/docs/html/reference/com/google/android/gms/maps/MapView.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + MapView | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - + @@ -789,7 +796,7 @@ Summary: - + @@ -799,7 +806,7 @@ Summary: - + @@ -811,7 +818,7 @@ Summary: - + @@ -1393,7 +1400,7 @@ android.view.View - + @@ -1464,7 +1471,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1783,7 +1790,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1794,7 +1801,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1805,7 +1812,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1816,7 +1823,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1827,7 +1834,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1860,7 +1867,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1871,7 +1878,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1893,7 +1900,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1904,7 +1911,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1949,7 +1956,7 @@ android.view.View @@ -1965,7 +1972,7 @@ android.view.View @@ -1981,7 +1988,7 @@ android.view.View @@ -1997,7 +2004,7 @@ android.view.View @@ -2044,7 +2051,7 @@ android.view.View void @@ -2202,7 +2209,7 @@ From class void @@ -2234,7 +2241,7 @@ From class boolean @@ -2247,7 +2254,7 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams @@ -2279,10 +2286,10 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams @@ -2311,7 +2318,7 @@ From class - Drawable + Drawable @@ -2458,7 +2465,7 @@ From class void @@ -2522,7 +2529,7 @@ From class boolean @@ -2542,7 +2549,7 @@ From class class="jd-expando-trigger-img" /> From class - android.view.ViewGroup + android.view.ViewGroup
          @@ -2580,7 +2587,7 @@ From class void @@ -2612,7 +2619,7 @@ From class void @@ -2628,7 +2635,7 @@ From class void @@ -2644,7 +2651,7 @@ From class void @@ -2660,7 +2667,7 @@ From class void @@ -2676,7 +2683,7 @@ From class void @@ -2692,7 +2699,7 @@ From class void @@ -2708,7 +2715,7 @@ From class boolean @@ -2724,7 +2731,7 @@ From class boolean @@ -2740,7 +2747,7 @@ From class void @@ -2756,7 +2763,7 @@ From class void @@ -2772,7 +2779,7 @@ From class void @@ -2804,7 +2811,7 @@ From class boolean @@ -2820,7 +2827,7 @@ From class void @@ -2836,7 +2843,7 @@ From class void @@ -2852,7 +2859,7 @@ From class void @@ -2932,7 +2939,7 @@ From class void @@ -2980,7 +2987,7 @@ From class void @@ -3028,7 +3035,7 @@ From class void @@ -3044,7 +3051,7 @@ From class void @@ -3060,7 +3067,7 @@ From class boolean @@ -3076,7 +3083,7 @@ From class boolean @@ -3092,7 +3099,7 @@ From class boolean @@ -3108,7 +3115,7 @@ From class boolean @@ -3124,7 +3131,7 @@ From class boolean @@ -3140,7 +3147,7 @@ From class boolean @@ -3156,7 +3163,7 @@ From class void @@ -3172,7 +3179,7 @@ From class void @@ -3252,7 +3259,7 @@ From class void @@ -3268,7 +3275,7 @@ From class boolean @@ -3284,7 +3291,7 @@ From class boolean @@ -3300,7 +3307,7 @@ From class boolean @@ -3316,7 +3323,7 @@ From class void @@ -3380,7 +3387,7 @@ From class boolean @@ -3412,7 +3419,7 @@ From class void @@ -3425,7 +3432,7 @@ From class - View + View @@ -3460,7 +3467,7 @@ From class boolean @@ -3473,10 +3480,10 @@ From class - View + View @@ -3492,7 +3499,7 @@ From class void @@ -3508,7 +3515,7 @@ From class boolean @@ -3521,7 +3528,7 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams @@ -3553,10 +3560,10 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams @@ -3569,7 +3576,7 @@ From class - View + View @@ -3652,7 +3659,7 @@ From class boolean @@ -3681,7 +3688,7 @@ From class - View + View @@ -3812,7 +3819,7 @@ From class void @@ -3825,10 +3832,10 @@ From class - ViewParent + ViewParent @@ -3956,7 +3963,7 @@ From class void @@ -3972,7 +3979,7 @@ From class void @@ -4004,7 +4011,7 @@ From class void @@ -4020,7 +4027,7 @@ From class void @@ -4084,7 +4091,7 @@ From class boolean @@ -4100,7 +4107,7 @@ From class boolean @@ -4132,7 +4139,7 @@ From class boolean @@ -4148,7 +4155,7 @@ From class boolean @@ -4164,7 +4171,7 @@ From class void @@ -4212,7 +4219,7 @@ From class void @@ -4228,7 +4235,7 @@ From class void @@ -4260,7 +4267,7 @@ From class void @@ -4308,7 +4315,7 @@ From class void @@ -4324,7 +4331,7 @@ From class boolean @@ -4356,7 +4363,7 @@ From class boolean @@ -4372,7 +4379,7 @@ From class boolean @@ -4388,7 +4395,7 @@ From class void @@ -4564,7 +4571,7 @@ From class void @@ -4580,7 +4587,7 @@ From class void @@ -4628,7 +4635,7 @@ From class void @@ -4708,7 +4715,7 @@ From class boolean @@ -4724,7 +4731,7 @@ From class ActionMode @@ -4756,7 +4763,7 @@ From class void @@ -4772,7 +4779,7 @@ From class void @@ -4792,7 +4799,7 @@ From class class="jd-expando-trigger-img" /> From class - android.view.View + android.view.View
          @@ -4830,7 +4837,7 @@ From class void @@ -4846,7 +4853,7 @@ From class void @@ -4894,7 +4901,7 @@ From class void @@ -4926,7 +4933,7 @@ From class void @@ -5118,7 +5125,7 @@ From class boolean @@ -5310,7 +5317,7 @@ From class void @@ -5342,7 +5349,7 @@ From class void @@ -5390,7 +5397,7 @@ From class void @@ -5406,7 +5413,7 @@ From class boolean @@ -5422,7 +5429,7 @@ From class boolean @@ -5438,7 +5445,7 @@ From class boolean @@ -5454,7 +5461,7 @@ From class boolean @@ -5470,7 +5477,7 @@ From class boolean @@ -5486,7 +5493,7 @@ From class boolean @@ -5502,7 +5509,7 @@ From class boolean @@ -5518,7 +5525,7 @@ From class boolean @@ -5534,7 +5541,7 @@ From class void @@ -5550,7 +5557,7 @@ From class void @@ -5630,7 +5637,7 @@ From class boolean @@ -5646,7 +5653,7 @@ From class boolean @@ -5662,7 +5669,7 @@ From class boolean @@ -5678,7 +5685,7 @@ From class void @@ -5742,7 +5749,7 @@ From class void @@ -5771,7 +5778,7 @@ From class - View + View @@ -5822,7 +5829,7 @@ From class void @@ -5838,7 +5845,7 @@ From class boolean @@ -5851,7 +5858,7 @@ From class - View + View @@ -6251,7 +6258,7 @@ From class - ArrayList<View> + ArrayList<View> @@ -6286,7 +6293,7 @@ From class boolean @@ -6302,7 +6309,7 @@ From class boolean @@ -6315,7 +6322,7 @@ From class - Handler + Handler @@ -6443,7 +6450,7 @@ From class - KeyEvent.DispatcherState + KeyEvent.DispatcherState @@ -6587,7 +6594,7 @@ From class - Matrix + Matrix @@ -7723,10 +7730,10 @@ From class static - View + View @@ -7742,7 +7749,7 @@ From class void @@ -7758,7 +7765,7 @@ From class void @@ -7774,7 +7781,7 @@ From class void @@ -7822,7 +7829,7 @@ From class void @@ -8478,7 +8485,7 @@ From class void @@ -8494,7 +8501,7 @@ From class void @@ -8523,10 +8530,10 @@ From class - InputConnection + InputConnection @@ -8590,7 +8597,7 @@ From class void @@ -8606,7 +8613,7 @@ From class void @@ -8622,7 +8629,7 @@ From class boolean @@ -8670,7 +8677,7 @@ From class void @@ -8686,7 +8693,7 @@ From class boolean @@ -8718,7 +8725,7 @@ From class boolean @@ -8734,7 +8741,7 @@ From class void @@ -8766,7 +8773,7 @@ From class boolean @@ -8782,7 +8789,7 @@ From class boolean @@ -8798,7 +8805,7 @@ From class boolean @@ -8814,7 +8821,7 @@ From class boolean @@ -8830,7 +8837,7 @@ From class boolean @@ -8846,7 +8853,7 @@ From class boolean @@ -8910,7 +8917,7 @@ From class void @@ -8926,7 +8933,7 @@ From class void @@ -8939,7 +8946,7 @@ From class - Parcelable + Parcelable @@ -9054,7 +9061,7 @@ From class boolean @@ -9070,7 +9077,7 @@ From class void @@ -9150,7 +9157,7 @@ From class boolean @@ -9246,7 +9253,7 @@ From class boolean @@ -9262,7 +9269,7 @@ From class boolean @@ -9374,7 +9381,7 @@ From class void @@ -9390,7 +9397,7 @@ From class void @@ -9422,7 +9429,7 @@ From class boolean @@ -9486,7 +9493,7 @@ From class boolean @@ -9566,7 +9573,7 @@ From class boolean @@ -9582,7 +9589,7 @@ From class boolean @@ -9630,7 +9637,7 @@ From class void @@ -9646,7 +9653,7 @@ From class void @@ -9662,7 +9669,7 @@ From class void @@ -9726,7 +9733,7 @@ From class void @@ -9790,7 +9797,7 @@ From class void @@ -9806,7 +9813,7 @@ From class void @@ -9838,7 +9845,7 @@ From class void @@ -9918,7 +9925,7 @@ From class void @@ -10222,7 +10229,7 @@ From class void @@ -10238,7 +10245,7 @@ From class void @@ -10414,7 +10421,7 @@ From class void @@ -10430,7 +10437,7 @@ From class void @@ -10462,7 +10469,7 @@ From class void @@ -10510,7 +10517,7 @@ From class void @@ -10526,7 +10533,7 @@ From class void @@ -10558,7 +10565,7 @@ From class void @@ -10958,7 +10965,7 @@ From class void @@ -10974,7 +10981,7 @@ From class void @@ -11006,7 +11013,7 @@ From class void @@ -11214,7 +11221,7 @@ From class void @@ -11230,7 +11237,7 @@ From class boolean @@ -11246,7 +11253,7 @@ From class void @@ -11262,7 +11269,7 @@ From class void @@ -11278,7 +11285,7 @@ From class boolean @@ -11330,7 +11337,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -11397,7 +11404,7 @@ From class final - Class<?> + Class<?> @@ -11570,7 +11577,7 @@ From interface void @@ -11586,7 +11593,7 @@ From interface void @@ -11606,7 +11613,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.KeyEvent.Callback + android.view.KeyEvent.Callback
          @@ -11644,7 +11651,7 @@ From interface boolean @@ -11660,7 +11667,7 @@ From interface boolean @@ -11676,7 +11683,7 @@ From interface boolean @@ -11696,7 +11703,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.ViewManager + android.view.ViewManager
          @@ -11734,7 +11741,7 @@ From interface void @@ -11750,7 +11757,7 @@ From interface void @@ -11770,7 +11777,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.ViewParent + android.view.ViewParent
          @@ -11808,7 +11815,7 @@ From interface void @@ -11824,7 +11831,7 @@ From interface void @@ -11840,7 +11847,7 @@ From interface void @@ -11853,10 +11860,10 @@ From interface - View + View @@ -11872,7 +11879,7 @@ From interface void @@ -11888,7 +11895,7 @@ From interface boolean @@ -11901,7 +11908,7 @@ From interface - ViewParent + ViewParent @@ -11949,10 +11956,10 @@ From interface - ViewParent + ViewParent @@ -11984,7 +11991,7 @@ From interface void @@ -12000,7 +12007,7 @@ From interface void @@ -12016,7 +12023,7 @@ From interface boolean @@ -12080,7 +12087,7 @@ From interface boolean @@ -12096,7 +12103,7 @@ From interface void @@ -12112,7 +12119,7 @@ From interface boolean @@ -12128,7 +12135,7 @@ From interface ActionMode @@ -12148,7 +12155,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.accessibility.AccessibilityEventSource + android.view.accessibility.AccessibilityEventSource
          @@ -12244,7 +12251,7 @@ From interface MapView - (Context context) + (Context context)
          @@ -12273,7 +12280,7 @@ From interface MapView - (Context context, AttributeSet attrs) + (Context context, AttributeSet attrs)
          @@ -12302,7 +12309,7 @@ From interface MapView - (Context context, AttributeSet attrs, int defStyle) + (Context context, AttributeSet attrs, int defStyle)
          @@ -12331,7 +12338,7 @@ From interface MapView - (Context context, GoogleMapOptions options) + (Context context, GoogleMapOptions options)
          @@ -12411,7 +12418,7 @@ From interface void onCreate - (Bundle savedInstanceState) + (Bundle savedInstanceState)
          @@ -12581,7 +12588,7 @@ From interface void onSaveInstanceState - (Bundle outState) + (Bundle outState)
          diff --git a/docs/html/reference/com/google/android/gms/maps/MapsInitializer.html b/docs/html/reference/com/google/android/gms/maps/MapsInitializer.html index a7c65aba870f0d0c6ca930c0075d50ce0cd6dad1..a46f71a86b3a56a54a4ac0b8cebcabf6040ca336 100644 --- a/docs/html/reference/com/google/android/gms/maps/MapsInitializer.html +++ b/docs/html/reference/com/google/android/gms/maps/MapsInitializer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + MapsInitializer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - + @@ -799,7 +806,7 @@ Summary: void @@ -896,7 +903,7 @@ From class final - Class<?> + Class<?> - + @@ -795,7 +802,7 @@ Summary: LatLng @@ -929,7 +936,7 @@ From class final - Class<?> + Class<?> - + @@ -944,7 +951,7 @@ Summary: void @@ -960,7 +967,7 @@ Summary: void @@ -976,7 +983,7 @@ Summary: void @@ -989,10 +996,10 @@ Summary: - View + View @@ -1040,7 +1047,7 @@ Summary: void @@ -1122,7 +1129,7 @@ Summary: void @@ -1172,7 +1179,7 @@ From class void @@ -1188,7 +1195,7 @@ From class boolean @@ -1217,7 +1224,7 @@ From class final - Bundle + Bundle @@ -1329,7 +1336,7 @@ From class final - Resources + Resources @@ -1393,7 +1400,7 @@ From class final - String + String @@ -1540,7 +1547,7 @@ From class Fragment @@ -1684,7 +1691,7 @@ From class void @@ -1700,7 +1707,7 @@ From class void @@ -1716,7 +1723,7 @@ From class void @@ -1732,7 +1739,7 @@ From class void @@ -1748,7 +1755,7 @@ From class boolean @@ -1764,7 +1771,7 @@ From class void @@ -1777,7 +1784,7 @@ From class - Animation + Animation @@ -1812,7 +1819,7 @@ From class void @@ -1825,10 +1832,10 @@ From class - View + View @@ -1924,7 +1931,7 @@ From class void @@ -1956,7 +1963,7 @@ From class boolean @@ -1972,7 +1979,7 @@ From class void @@ -2004,7 +2011,7 @@ From class void @@ -2036,7 +2043,7 @@ From class void @@ -2084,7 +2091,7 @@ From class void @@ -2100,7 +2107,7 @@ From class void @@ -2116,7 +2123,7 @@ From class void @@ -2132,7 +2139,7 @@ From class void @@ -2244,7 +2251,7 @@ From class void @@ -2260,7 +2267,7 @@ From class void @@ -2273,7 +2280,7 @@ From class - String + String @@ -2312,7 +2319,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -2379,7 +2386,7 @@ From class final - Class<?> + Class<?> @@ -2572,7 +2579,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.View.OnCreateContextMenuListener + android.view.View.OnCreateContextMenuListener
          @@ -2797,7 +2804,7 @@ From interface void onActivityCreated - (Bundle savedInstanceState) + (Bundle savedInstanceState)
          @@ -2826,7 +2833,7 @@ From interface void onAttach - (Activity activity) + (Activity activity)
          @@ -2855,7 +2862,7 @@ From interface void onCreate - (Bundle savedInstanceState) + (Bundle savedInstanceState)
          @@ -2881,10 +2888,10 @@ From interface - View + View onCreateView - (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
          @@ -2971,7 +2978,7 @@ From interface void onInflate - (Activity activity, AttributeSet attrs, Bundle savedInstanceState) + (Activity activity, AttributeSet attrs, Bundle savedInstanceState)
          @@ -3088,7 +3095,7 @@ From interface void onSaveInstanceState - (Bundle outState) + (Bundle outState)
          @@ -3117,7 +3124,7 @@ From interface void setArguments - (Bundle args) + (Bundle args)
          diff --git a/docs/html/reference/com/google/android/gms/maps/UiSettings.html b/docs/html/reference/com/google/android/gms/maps/UiSettings.html index ebb7d92b51cdbfb09f867ac63c96cb6c9e6c9319..2b3aec5aa844b394ccd6f4f1a6d40e9503d8e21b 100644 --- a/docs/html/reference/com/google/android/gms/maps/UiSettings.html +++ b/docs/html/reference/com/google/android/gms/maps/UiSettings.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + UiSettings | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - + @@ -1076,7 +1083,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1143,7 +1150,7 @@ From class final - Class<?> + Class<?> - + @@ -805,7 +812,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -872,7 +879,7 @@ From class final - Class<?> + Class<?> - + @@ -923,7 +930,7 @@ Summary: BitmapDescriptor @@ -1093,7 +1100,7 @@ From class final - Class<?> + Class<?> - + @@ -940,7 +947,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1007,7 +1014,7 @@ From class final - Class<?> + Class<?> - + @@ -1021,7 +1028,7 @@ android.os.Parcelable CameraPosition @@ -1103,7 +1110,7 @@ android.os.Parcelable - String + String @@ -1150,7 +1157,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1217,7 +1224,7 @@ From class final - Class<?> + Class<?> @@ -1643,11 +1650,11 @@ From interface
          Throws
          IllegalStateException + IllegalStateException if this provider is already inactive
          java.lang.Objectjava.lang.Object
          - onActivityCreated(Bundle savedInstanceState) + onActivityCreated(Bundle savedInstanceState)
          - onAttach(Activity activity) + onAttach(Activity activity)
          - onCreate(Bundle savedInstanceState) + onCreate(Bundle savedInstanceState)
          - onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
          - onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) + onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState)
          Parse attributes during inflation from a view hierarchy into the arguments we handle.
          @@ -1207,7 +1214,7 @@ android.content.ComponentCallbacks2 void
          - onSaveInstanceState(Bundle outState) + onSaveInstanceState(Bundle outState)
          - setArguments(Bundle args) + setArguments(Bundle args)
          - dump(String arg0, FileDescriptor arg1, PrintWriter arg2, String[] arg3) + dump(String arg0, FileDescriptor arg1, PrintWriter arg2, String[] arg3)
          - equals(Object arg0) + equals(Object arg0)
          getActivity() @@ -1318,7 +1325,7 @@ From class final - Bundle + Bundle getArguments() @@ -1382,7 +1389,7 @@ From class final - Resources + Resources getResources() @@ -1414,7 +1421,7 @@ From class final - String + String getString(int arg0) @@ -1430,10 +1437,10 @@ From class final - String + String - getString(int arg0, Object... arg1) + getString(int arg0, Object... arg1)
          getTag() @@ -1494,7 +1501,7 @@ From class final - CharSequence + CharSequence getText(int arg0) @@ -1526,7 +1533,7 @@ From class - View + View getView() @@ -1561,7 +1568,7 @@ From class Fragment - instantiate(Context arg0, String arg1) + instantiate(Context arg0, String arg1)
          - instantiate(Context arg0, String arg1, Bundle arg2) + instantiate(Context arg0, String arg1, Bundle arg2)
          - onActivityCreated(Bundle arg0) + onActivityCreated(Bundle arg0)
          - onActivityResult(int arg0, int arg1, Intent arg2) + onActivityResult(int arg0, int arg1, Intent arg2)
          - onAttach(Activity arg0) + onAttach(Activity arg0)
          - onConfigurationChanged(Configuration arg0) + onConfigurationChanged(Configuration arg0)
          - onContextItemSelected(MenuItem arg0) + onContextItemSelected(MenuItem arg0)
          - onCreate(Bundle arg0) + onCreate(Bundle arg0)
          - onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2) + onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2)
          - onCreateOptionsMenu(Menu arg0, MenuInflater arg1) + onCreateOptionsMenu(Menu arg0, MenuInflater arg1)
          - onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2) + onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2)
          - onInflate(AttributeSet arg0, Bundle arg1) + onInflate(AttributeSet arg0, Bundle arg1)
          - onInflate(Activity arg0, AttributeSet arg1, Bundle arg2) + onInflate(Activity arg0, AttributeSet arg1, Bundle arg2)
          - onOptionsItemSelected(MenuItem arg0) + onOptionsItemSelected(MenuItem arg0)
          - onOptionsMenuClosed(Menu arg0) + onOptionsMenuClosed(Menu arg0)
          - onPrepareOptionsMenu(Menu arg0) + onPrepareOptionsMenu(Menu arg0)
          - onSaveInstanceState(Bundle arg0) + onSaveInstanceState(Bundle arg0)
          - onViewCreated(View arg0, Bundle arg1) + onViewCreated(View arg0, Bundle arg1)
          - registerForContextMenu(View arg0) + registerForContextMenu(View arg0)
          - setArguments(Bundle arg0) + setArguments(Bundle arg0)
          - startActivity(Intent arg0) + startActivity(Intent arg0)
          - startActivity(Intent arg0, Bundle arg1) + startActivity(Intent arg0, Bundle arg1)
          - startActivityForResult(Intent arg0, int arg1) + startActivityForResult(Intent arg0, int arg1)
          - startActivityForResult(Intent arg0, int arg1, Bundle arg2) + startActivityForResult(Intent arg0, int arg1, Bundle arg2)
          toString() @@ -2361,7 +2368,7 @@ From class void - unregisterForContextMenu(View arg0) + unregisterForContextMenu(View arg0)
          clone() @@ -2419,7 +2426,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -2512,7 +2519,7 @@ From class - String + String toString() @@ -2583,7 +2590,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.content.ComponentCallbacks + android.content.ComponentCallbacks
          - onConfigurationChanged(Configuration arg0) + onConfigurationChanged(Configuration arg0)
          - onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2) + onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2)
          java.lang.Objectjava.lang.Object
             ↳android.view.Viewandroid.view.View
             ↳android.view.ViewGroupandroid.view.ViewGroup
             ↳android.widget.FrameLayoutandroid.widget.FrameLayout
          StringString VIEW_LOG_TAG
          ALPHA
          ROTATION
          ROTATION_X
          ROTATION_Y
          SCALE_X
          SCALE_Y
          TRANSLATION_X
          TRANSLATION_Y
          X
          Y
          - MapView(Context context) + MapView(Context context)
          - MapView(Context context, AttributeSet attrs) + MapView(Context context, AttributeSet attrs)
          - MapView(Context context, AttributeSet attrs, int defStyle) + MapView(Context context, AttributeSet attrs, int defStyle)
          - MapView(Context context, GoogleMapOptions options) + MapView(Context context, GoogleMapOptions options)
          - onCreate(Bundle savedInstanceState) + onCreate(Bundle savedInstanceState)
          You must call this method from the parent Activity/Fragment's corresponding method.
          @@ -2134,7 +2141,7 @@ android.view.View void
          - onSaveInstanceState(Bundle outState) + onSaveInstanceState(Bundle outState)
          You must call this method from the parent Activity/Fragment's corresponding method.
          @@ -2164,7 +2171,7 @@ android.view.View class="jd-expando-trigger-img" /> From class - android.widget.FrameLayout + android.widget.FrameLayout
          - checkLayoutParams(ViewGroup.LayoutParams arg0) + checkLayoutParams(ViewGroup.LayoutParams arg0)
          - draw(Canvas arg0) + draw(Canvas arg0)
          - gatherTransparentRegion(Region arg0) + gatherTransparentRegion(Region arg0)
          generateDefaultLayoutParams() @@ -2263,10 +2270,10 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams - generateLayoutParams(AttributeSet arg0) + generateLayoutParams(AttributeSet arg0)
          - generateLayoutParams(ViewGroup.LayoutParams arg0) + generateLayoutParams(ViewGroup.LayoutParams arg0)
          getForeground() @@ -2378,7 +2385,7 @@ From class void - onInitializeAccessibilityEvent(AccessibilityEvent arg0) + onInitializeAccessibilityEvent(AccessibilityEvent arg0)
          - setForeground(Drawable arg0) + setForeground(Drawable arg0)
          - verifyDrawable(Drawable arg0) + verifyDrawable(Drawable arg0)
          - addChildrenForAccessibility(ArrayList<View> arg0) + addChildrenForAccessibility(ArrayList<View> arg0)
          - addFocusables(ArrayList<View> arg0, int arg1, int arg2) + addFocusables(ArrayList<View> arg0, int arg1, int arg2)
          - addTouchables(ArrayList<View> arg0) + addTouchables(ArrayList<View> arg0)
          - addView(View arg0, int arg1, ViewGroup.LayoutParams arg2) + addView(View arg0, int arg1, ViewGroup.LayoutParams arg2)
          - addView(View arg0, ViewGroup.LayoutParams arg1) + addView(View arg0, ViewGroup.LayoutParams arg1)
          - addView(View arg0, int arg1) + addView(View arg0, int arg1)
          - addView(View arg0) + addView(View arg0)
          - addView(View arg0, int arg1, int arg2) + addView(View arg0, int arg1, int arg2)
          - addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2, boolean arg3) + addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2, boolean arg3)
          - addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2) + addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2)
          - attachLayoutAnimationParameters(View arg0, ViewGroup.LayoutParams arg1, int arg2, int arg3) + attachLayoutAnimationParameters(View arg0, ViewGroup.LayoutParams arg1, int arg2, int arg3)
          - attachViewToParent(View arg0, int arg1, ViewGroup.LayoutParams arg2) + attachViewToParent(View arg0, int arg1, ViewGroup.LayoutParams arg2)
          - bringChildToFront(View arg0) + bringChildToFront(View arg0)
          - checkLayoutParams(ViewGroup.LayoutParams arg0) + checkLayoutParams(ViewGroup.LayoutParams arg0)
          - childDrawableStateChanged(View arg0) + childDrawableStateChanged(View arg0)
          - cleanupLayoutState(View arg0) + cleanupLayoutState(View arg0)
          - clearChildFocus(View arg0) + clearChildFocus(View arg0)
          - detachViewFromParent(View arg0) + detachViewFromParent(View arg0)
          - dispatchConfigurationChanged(Configuration arg0) + dispatchConfigurationChanged(Configuration arg0)
          - dispatchDraw(Canvas arg0) + dispatchDraw(Canvas arg0)
          - dispatchFreezeSelfOnly(SparseArray<Parcelable> arg0) + dispatchFreezeSelfOnly(SparseArray<Parcelable> arg0)
          - dispatchGenericFocusedEvent(MotionEvent arg0) + dispatchGenericFocusedEvent(MotionEvent arg0)
          - dispatchGenericPointerEvent(MotionEvent arg0) + dispatchGenericPointerEvent(MotionEvent arg0)
          - dispatchHoverEvent(MotionEvent arg0) + dispatchHoverEvent(MotionEvent arg0)
          - dispatchKeyEvent(KeyEvent arg0) + dispatchKeyEvent(KeyEvent arg0)
          - dispatchKeyEventPreIme(KeyEvent arg0) + dispatchKeyEventPreIme(KeyEvent arg0)
          - dispatchKeyShortcutEvent(KeyEvent arg0) + dispatchKeyShortcutEvent(KeyEvent arg0)
          - dispatchRestoreInstanceState(SparseArray<Parcelable> arg0) + dispatchRestoreInstanceState(SparseArray<Parcelable> arg0)
          - dispatchSaveInstanceState(SparseArray<Parcelable> arg0) + dispatchSaveInstanceState(SparseArray<Parcelable> arg0)
          - dispatchThawSelfOnly(SparseArray<Parcelable> arg0) + dispatchThawSelfOnly(SparseArray<Parcelable> arg0)
          - dispatchTouchEvent(MotionEvent arg0) + dispatchTouchEvent(MotionEvent arg0)
          - dispatchTrackballEvent(MotionEvent arg0) + dispatchTrackballEvent(MotionEvent arg0)
          - dispatchUnhandledMove(View arg0, int arg1) + dispatchUnhandledMove(View arg0, int arg1)
          - dispatchVisibilityChanged(View arg0, int arg1) + dispatchVisibilityChanged(View arg0, int arg1)
          - drawChild(Canvas arg0, View arg1, long arg2) + drawChild(Canvas arg0, View arg1, long arg2)
          - endViewTransition(View arg0) + endViewTransition(View arg0)
          findFocus() @@ -3444,7 +3451,7 @@ From class void - findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2) + findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2)
          - fitSystemWindows(Rect arg0) + fitSystemWindows(Rect arg0)
          - focusSearch(View arg0, int arg1) + focusSearch(View arg0, int arg1)
          - focusableViewAvailable(View arg0) + focusableViewAvailable(View arg0)
          - gatherTransparentRegion(Region arg0) + gatherTransparentRegion(Region arg0)
          generateDefaultLayoutParams() @@ -3537,10 +3544,10 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams - generateLayoutParams(AttributeSet arg0) + generateLayoutParams(AttributeSet arg0)
          - generateLayoutParams(ViewGroup.LayoutParams arg0) + generateLayoutParams(ViewGroup.LayoutParams arg0)
          getChildAt(int arg0) @@ -3636,7 +3643,7 @@ From class boolean - getChildStaticTransformation(View arg0, Transformation arg1) + getChildStaticTransformation(View arg0, Transformation arg1)
          - getChildVisibleRect(View arg0, Rect arg1, Point arg2) + getChildVisibleRect(View arg0, Rect arg1, Point arg2)
          getFocusedChild() @@ -3697,7 +3704,7 @@ From class - LayoutAnimationController + LayoutAnimationController getLayoutAnimation() @@ -3713,7 +3720,7 @@ From class - Animation.AnimationListener + Animation.AnimationListener getLayoutAnimationListener() @@ -3796,7 +3803,7 @@ From class int - indexOfChild(View arg0) + indexOfChild(View arg0)
          - invalidateChild(View arg0, Rect arg1) + invalidateChild(View arg0, Rect arg1)
          - invalidateChildInParent(int[] arg0, Rect arg1) + invalidateChildInParent(int[] arg0, Rect arg1)
          - measureChild(View arg0, int arg1, int arg2) + measureChild(View arg0, int arg1, int arg2)
          - measureChildWithMargins(View arg0, int arg1, int arg2, int arg3, int arg4) + measureChildWithMargins(View arg0, int arg1, int arg2, int arg3, int arg4)
          - offsetDescendantRectToMyCoords(View arg0, Rect arg1) + offsetDescendantRectToMyCoords(View arg0, Rect arg1)
          - offsetRectIntoDescendantCoords(View arg0, Rect arg1) + offsetRectIntoDescendantCoords(View arg0, Rect arg1)
          - onInterceptHoverEvent(MotionEvent arg0) + onInterceptHoverEvent(MotionEvent arg0)
          - onInterceptTouchEvent(MotionEvent arg0) + onInterceptTouchEvent(MotionEvent arg0)
          - onRequestFocusInDescendants(int arg0, Rect arg1) + onRequestFocusInDescendants(int arg0, Rect arg1)
          - onRequestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1) + onRequestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1)
          - recomputeViewAttributes(View arg0) + recomputeViewAttributes(View arg0)
          - removeDetachedView(View arg0, boolean arg1) + removeDetachedView(View arg0, boolean arg1)
          - removeView(View arg0) + removeView(View arg0)
          - removeViewInLayout(View arg0) + removeViewInLayout(View arg0)
          - requestChildFocus(View arg0, View arg1) + requestChildFocus(View arg0, View arg1)
          - requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2) + requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2)
          - requestFocus(int arg0, Rect arg1) + requestFocus(int arg0, Rect arg1)
          - requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1) + requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1)
          - requestTransparentRegion(View arg0) + requestTransparentRegion(View arg0)
          - setLayoutAnimation(LayoutAnimationController arg0) + setLayoutAnimation(LayoutAnimationController arg0)
          - setLayoutAnimationListener(Animation.AnimationListener arg0) + setLayoutAnimationListener(Animation.AnimationListener arg0)
          - setOnHierarchyChangeListener(ViewGroup.OnHierarchyChangeListener arg0) + setOnHierarchyChangeListener(ViewGroup.OnHierarchyChangeListener arg0)
          - showContextMenuForChild(View arg0) + showContextMenuForChild(View arg0)
          - startActionModeForChild(View arg0, ActionMode.Callback arg1) + startActionModeForChild(View arg0, ActionMode.Callback arg1)
          - startViewTransition(View arg0) + startViewTransition(View arg0)
          - updateViewLayout(View arg0, ViewGroup.LayoutParams arg1) + updateViewLayout(View arg0, ViewGroup.LayoutParams arg1)
          - addChildrenForAccessibility(ArrayList<View> arg0) + addChildrenForAccessibility(ArrayList<View> arg0)
          - addFocusables(ArrayList<View> arg0, int arg1, int arg2) + addFocusables(ArrayList<View> arg0, int arg1, int arg2)
          - addFocusables(ArrayList<View> arg0, int arg1) + addFocusables(ArrayList<View> arg0, int arg1)
          - addTouchables(ArrayList<View> arg0) + addTouchables(ArrayList<View> arg0)
          - announceForAccessibility(CharSequence arg0) + announceForAccessibility(CharSequence arg0)
          - checkInputConnectionProxy(View arg0) + checkInputConnectionProxy(View arg0)
          - createContextMenu(ContextMenu arg0) + createContextMenu(ContextMenu arg0)
          - dispatchConfigurationChanged(Configuration arg0) + dispatchConfigurationChanged(Configuration arg0)
          - dispatchDraw(Canvas arg0) + dispatchDraw(Canvas arg0)
          - dispatchGenericFocusedEvent(MotionEvent arg0) + dispatchGenericFocusedEvent(MotionEvent arg0)
          - dispatchGenericMotionEvent(MotionEvent arg0) + dispatchGenericMotionEvent(MotionEvent arg0)
          - dispatchGenericPointerEvent(MotionEvent arg0) + dispatchGenericPointerEvent(MotionEvent arg0)
          - dispatchHoverEvent(MotionEvent arg0) + dispatchHoverEvent(MotionEvent arg0)
          - dispatchKeyEvent(KeyEvent arg0) + dispatchKeyEvent(KeyEvent arg0)
          - dispatchKeyEventPreIme(KeyEvent arg0) + dispatchKeyEventPreIme(KeyEvent arg0)
          - dispatchKeyShortcutEvent(KeyEvent arg0) + dispatchKeyShortcutEvent(KeyEvent arg0)
          - dispatchPopulateAccessibilityEvent(AccessibilityEvent arg0) + dispatchPopulateAccessibilityEvent(AccessibilityEvent arg0)
          - dispatchRestoreInstanceState(SparseArray<Parcelable> arg0) + dispatchRestoreInstanceState(SparseArray<Parcelable> arg0)
          - dispatchSaveInstanceState(SparseArray<Parcelable> arg0) + dispatchSaveInstanceState(SparseArray<Parcelable> arg0)
          - dispatchTouchEvent(MotionEvent arg0) + dispatchTouchEvent(MotionEvent arg0)
          - dispatchTrackballEvent(MotionEvent arg0) + dispatchTrackballEvent(MotionEvent arg0)
          - dispatchUnhandledMove(View arg0, int arg1) + dispatchUnhandledMove(View arg0, int arg1)
          - dispatchVisibilityChanged(View arg0, int arg1) + dispatchVisibilityChanged(View arg0, int arg1)
          - draw(Canvas arg0) + draw(Canvas arg0)
          findFocus() @@ -5787,7 +5794,7 @@ From class final - View + View findViewById(int arg0) @@ -5803,10 +5810,10 @@ From class final - View + View - findViewWithTag(Object arg0) + findViewWithTag(Object arg0)
          - findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2) + findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2)
          - fitSystemWindows(Rect arg0) + fitSystemWindows(Rect arg0)
          focusSearch(int arg0) @@ -5915,7 +5922,7 @@ From class - Animation + Animation getAnimation() @@ -5931,7 +5938,7 @@ From class - IBinder + IBinder getApplicationWindowToken() @@ -5947,7 +5954,7 @@ From class - Drawable + Drawable getBackground() @@ -6043,7 +6050,7 @@ From class - CharSequence + CharSequence getContentDescription() @@ -6059,7 +6066,7 @@ From class final - Context + Context getContext() @@ -6075,7 +6082,7 @@ From class - ContextMenu.ContextMenuInfo + ContextMenu.ContextMenuInfo getContextMenuInfo() @@ -6123,7 +6130,7 @@ From class - Bitmap + Bitmap getDrawingCache(boolean arg0) @@ -6139,7 +6146,7 @@ From class - Bitmap + Bitmap getDrawingCache() @@ -6190,7 +6197,7 @@ From class void - getDrawingRect(Rect arg0) + getDrawingRect(Rect arg0)
          getFocusables(int arg0) @@ -6270,7 +6277,7 @@ From class void - getFocusedRect(Rect arg0) + getFocusedRect(Rect arg0)
          - getGlobalVisibleRect(Rect arg0, Point arg1) + getGlobalVisibleRect(Rect arg0, Point arg1)
          - getGlobalVisibleRect(Rect arg0) + getGlobalVisibleRect(Rect arg0)
          getHandler() @@ -6350,7 +6357,7 @@ From class void - getHitRect(Rect arg0) + getHitRect(Rect arg0)
          getKeyDispatcherState() @@ -6475,7 +6482,7 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams getLayoutParams() @@ -6542,7 +6549,7 @@ From class boolean - getLocalVisibleRect(Rect arg0) + getLocalVisibleRect(Rect arg0)
          getMatrix() @@ -6795,7 +6802,7 @@ From class - View.OnFocusChangeListener + View.OnFocusChangeListener getOnFocusChangeListener() @@ -6891,7 +6898,7 @@ From class final - ViewParent + ViewParent getParent() @@ -6907,7 +6914,7 @@ From class - ViewParent + ViewParent getParentForAccessibility() @@ -6955,7 +6962,7 @@ From class - Resources + Resources getResources() @@ -7019,7 +7026,7 @@ From class - View + View getRootView() @@ -7275,7 +7282,7 @@ From class - Object + Object getTag(int arg0) @@ -7291,7 +7298,7 @@ From class - Object + Object getTag() @@ -7355,7 +7362,7 @@ From class - TouchDelegate + TouchDelegate getTouchDelegate() @@ -7371,7 +7378,7 @@ From class - ArrayList<View> + ArrayList<View> getTouchables() @@ -7467,7 +7474,7 @@ From class - ViewTreeObserver + ViewTreeObserver getViewTreeObserver() @@ -7547,7 +7554,7 @@ From class - IBinder + IBinder getWindowToken() @@ -7582,7 +7589,7 @@ From class void - getWindowVisibleDisplayFrame(Rect arg0) + getWindowVisibleDisplayFrame(Rect arg0)
          - inflate(Context arg0, int arg1, ViewGroup arg2) + inflate(Context arg0, int arg1, ViewGroup arg2)
          - initializeFadingEdge(TypedArray arg0) + initializeFadingEdge(TypedArray arg0)
          - initializeScrollbars(TypedArray arg0) + initializeScrollbars(TypedArray arg0)
          - invalidate(Rect arg0) + invalidate(Rect arg0)
          - invalidateDrawable(Drawable arg0) + invalidateDrawable(Drawable arg0)
          - onConfigurationChanged(Configuration arg0) + onConfigurationChanged(Configuration arg0)
          - onCreateContextMenu(ContextMenu arg0) + onCreateContextMenu(ContextMenu arg0)
          - onCreateInputConnection(EditorInfo arg0) + onCreateInputConnection(EditorInfo arg0)
          - onDraw(Canvas arg0) + onDraw(Canvas arg0)
          - onDrawScrollBars(Canvas arg0) + onDrawScrollBars(Canvas arg0)
          - onFilterTouchEventForSecurity(MotionEvent arg0) + onFilterTouchEventForSecurity(MotionEvent arg0)
          - onFocusChanged(boolean arg0, int arg1, Rect arg2) + onFocusChanged(boolean arg0, int arg1, Rect arg2)
          - onGenericMotionEvent(MotionEvent arg0) + onGenericMotionEvent(MotionEvent arg0)
          - onHoverEvent(MotionEvent arg0) + onHoverEvent(MotionEvent arg0)
          - onInitializeAccessibilityEvent(AccessibilityEvent arg0) + onInitializeAccessibilityEvent(AccessibilityEvent arg0)
          - onKeyDown(int arg0, KeyEvent arg1) + onKeyDown(int arg0, KeyEvent arg1)
          - onKeyLongPress(int arg0, KeyEvent arg1) + onKeyLongPress(int arg0, KeyEvent arg1)
          - onKeyMultiple(int arg0, int arg1, KeyEvent arg2) + onKeyMultiple(int arg0, int arg1, KeyEvent arg2)
          - onKeyPreIme(int arg0, KeyEvent arg1) + onKeyPreIme(int arg0, KeyEvent arg1)
          - onKeyShortcut(int arg0, KeyEvent arg1) + onKeyShortcut(int arg0, KeyEvent arg1)
          - onKeyUp(int arg0, KeyEvent arg1) + onKeyUp(int arg0, KeyEvent arg1)
          - onPopulateAccessibilityEvent(AccessibilityEvent arg0) + onPopulateAccessibilityEvent(AccessibilityEvent arg0)
          - onRestoreInstanceState(Parcelable arg0) + onRestoreInstanceState(Parcelable arg0)
          onSaveInstanceState() @@ -9038,7 +9045,7 @@ From class boolean - onTouchEvent(MotionEvent arg0) + onTouchEvent(MotionEvent arg0)
          - onTrackballEvent(MotionEvent arg0) + onTrackballEvent(MotionEvent arg0)
          - onVisibilityChanged(View arg0, int arg1) + onVisibilityChanged(View arg0, int arg1)
          - performAccessibilityAction(int arg0, Bundle arg1) + performAccessibilityAction(int arg0, Bundle arg1)
          - post(Runnable arg0) + post(Runnable arg0)
          - postDelayed(Runnable arg0, long arg1) + postDelayed(Runnable arg0, long arg1)
          - postOnAnimation(Runnable arg0) + postOnAnimation(Runnable arg0)
          - postOnAnimationDelayed(Runnable arg0, long arg1) + postOnAnimationDelayed(Runnable arg0, long arg1)
          - removeCallbacks(Runnable arg0) + removeCallbacks(Runnable arg0)
          - requestFocus(int arg0, Rect arg1) + requestFocus(int arg0, Rect arg1)
          - requestRectangleOnScreen(Rect arg0) + requestRectangleOnScreen(Rect arg0)
          - requestRectangleOnScreen(Rect arg0, boolean arg1) + requestRectangleOnScreen(Rect arg0, boolean arg1)
          - restoreHierarchyState(SparseArray<Parcelable> arg0) + restoreHierarchyState(SparseArray<Parcelable> arg0)
          - saveHierarchyState(SparseArray<Parcelable> arg0) + saveHierarchyState(SparseArray<Parcelable> arg0)
          - scheduleDrawable(Drawable arg0, Runnable arg1, long arg2) + scheduleDrawable(Drawable arg0, Runnable arg1, long arg2)
          - sendAccessibilityEventUnchecked(AccessibilityEvent arg0) + sendAccessibilityEventUnchecked(AccessibilityEvent arg0)
          - setAnimation(Animation arg0) + setAnimation(Animation arg0)
          - setBackground(Drawable arg0) + setBackground(Drawable arg0)
          - setBackgroundDrawable(Drawable arg0) + setBackgroundDrawable(Drawable arg0)
          - setContentDescription(CharSequence arg0) + setContentDescription(CharSequence arg0)
          - setLayerType(int arg0, Paint arg1) + setLayerType(int arg0, Paint arg1)
          - setLayoutParams(ViewGroup.LayoutParams arg0) + setLayoutParams(ViewGroup.LayoutParams arg0)
          - setOnClickListener(View.OnClickListener arg0) + setOnClickListener(View.OnClickListener arg0)
          - setOnCreateContextMenuListener(View.OnCreateContextMenuListener arg0) + setOnCreateContextMenuListener(View.OnCreateContextMenuListener arg0)
          - setOnFocusChangeListener(View.OnFocusChangeListener arg0) + setOnFocusChangeListener(View.OnFocusChangeListener arg0)
          - setOnKeyListener(View.OnKeyListener arg0) + setOnKeyListener(View.OnKeyListener arg0)
          - setOnLongClickListener(View.OnLongClickListener arg0) + setOnLongClickListener(View.OnLongClickListener arg0)
          - setOnTouchListener(View.OnTouchListener arg0) + setOnTouchListener(View.OnTouchListener arg0)
          - setTag(int arg0, Object arg1) + setTag(int arg0, Object arg1)
          - setTag(Object arg0) + setTag(Object arg0)
          - setTouchDelegate(TouchDelegate arg0) + setTouchDelegate(TouchDelegate arg0)
          - startAnimation(Animation arg0) + startAnimation(Animation arg0)
          - startDrag(ClipData arg0, View.DragShadowBuilder arg1, Object arg2, int arg3) + startDrag(ClipData arg0, View.DragShadowBuilder arg1, Object arg2, int arg3)
          - unscheduleDrawable(Drawable arg0) + unscheduleDrawable(Drawable arg0)
          - unscheduleDrawable(Drawable arg0, Runnable arg1) + unscheduleDrawable(Drawable arg0, Runnable arg1)
          - verifyDrawable(Drawable arg0) + verifyDrawable(Drawable arg0)
          clone() @@ -11368,7 +11375,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -11461,7 +11468,7 @@ From class - String + String toString() @@ -11532,7 +11539,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.graphics.drawable.Drawable.Callback + android.graphics.drawable.Drawable.Callback
          - invalidateDrawable(Drawable arg0) + invalidateDrawable(Drawable arg0)
          - scheduleDrawable(Drawable arg0, Runnable arg1, long arg2) + scheduleDrawable(Drawable arg0, Runnable arg1, long arg2)
          - unscheduleDrawable(Drawable arg0, Runnable arg1) + unscheduleDrawable(Drawable arg0, Runnable arg1)
          - onKeyDown(int arg0, KeyEvent arg1) + onKeyDown(int arg0, KeyEvent arg1)
          - onKeyLongPress(int arg0, KeyEvent arg1) + onKeyLongPress(int arg0, KeyEvent arg1)
          - onKeyMultiple(int arg0, int arg1, KeyEvent arg2) + onKeyMultiple(int arg0, int arg1, KeyEvent arg2)
          - onKeyUp(int arg0, KeyEvent arg1) + onKeyUp(int arg0, KeyEvent arg1)
          - addView(View arg0, ViewGroup.LayoutParams arg1) + addView(View arg0, ViewGroup.LayoutParams arg1)
          - removeView(View arg0) + removeView(View arg0)
          - updateViewLayout(View arg0, ViewGroup.LayoutParams arg1) + updateViewLayout(View arg0, ViewGroup.LayoutParams arg1)
          - bringChildToFront(View arg0) + bringChildToFront(View arg0)
          - childDrawableStateChanged(View arg0) + childDrawableStateChanged(View arg0)
          - clearChildFocus(View arg0) + clearChildFocus(View arg0)
          - createContextMenu(ContextMenu arg0) + createContextMenu(ContextMenu arg0)
          - focusSearch(View arg0, int arg1) + focusSearch(View arg0, int arg1)
          - focusableViewAvailable(View arg0) + focusableViewAvailable(View arg0)
          - getChildVisibleRect(View arg0, Rect arg1, Point arg2) + getChildVisibleRect(View arg0, Rect arg1, Point arg2)
          getParent() @@ -11917,7 +11924,7 @@ From interface - ViewParent + ViewParent getParentForAccessibility() @@ -11936,7 +11943,7 @@ From interface void - invalidateChild(View arg0, Rect arg1) + invalidateChild(View arg0, Rect arg1)
          - invalidateChildInParent(int[] arg0, Rect arg1) + invalidateChildInParent(int[] arg0, Rect arg1)
          - recomputeViewAttributes(View arg0) + recomputeViewAttributes(View arg0)
          - requestChildFocus(View arg0, View arg1) + requestChildFocus(View arg0, View arg1)
          - requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2) + requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2)
          - requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1) + requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1)
          - requestTransparentRegion(View arg0) + requestTransparentRegion(View arg0)
          - showContextMenuForChild(View arg0) + showContextMenuForChild(View arg0)
          - startActionModeForChild(View arg0, ActionMode.Callback arg1) + startActionModeForChild(View arg0, ActionMode.Callback arg1)
          - sendAccessibilityEventUnchecked(AccessibilityEvent arg0) + sendAccessibilityEventUnchecked(AccessibilityEvent arg0)
          java.lang.Objectjava.lang.Object
          - initialize(Context context) + initialize(Context context)
          Initializes the Google Maps Android API so that its classes are ready for use.
          @@ -829,7 +836,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -867,7 +874,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -960,7 +967,7 @@ From class - String + String toString() @@ -1077,7 +1084,7 @@ From class void initialize - (Context context) + (Context context)
          diff --git a/docs/html/reference/com/google/android/gms/maps/Projection.html b/docs/html/reference/com/google/android/gms/maps/Projection.html index d75fd9be846a110c4c25f4703559b699cfbb5528..1d0412fc99ca41dbbfd5e5e5d35f358984a0c7ae 100644 --- a/docs/html/reference/com/google/android/gms/maps/Projection.html +++ b/docs/html/reference/com/google/android/gms/maps/Projection.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Projection | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - fromScreenLocation(Point point) + fromScreenLocation(Point point)
          Returns the geographic location that corresponds to a screen location.
          @@ -829,7 +836,7 @@ Summary: - Point + Point
          toScreenLocation(LatLng location) @@ -862,7 +869,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -900,7 +907,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -993,7 +1000,7 @@ From class - String + String toString() @@ -1110,7 +1117,7 @@ From class LatLng fromScreenLocation - (Point point) + (Point point)
          @@ -1189,7 +1196,7 @@ From class - Point + Point toScreenLocation (LatLng location) diff --git a/docs/html/reference/com/google/android/gms/maps/SupportMapFragment.html b/docs/html/reference/com/google/android/gms/maps/SupportMapFragment.html index 1750f2cb490d963f8fa8d2decfd050b1b003cf66..773b7f63202e6a5e99489612c0ca98121cdbabf8 100644 --- a/docs/html/reference/com/google/android/gms/maps/SupportMapFragment.html +++ b/docs/html/reference/com/google/android/gms/maps/SupportMapFragment.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + SupportMapFragment | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - onActivityCreated(Bundle savedInstanceState) + onActivityCreated(Bundle savedInstanceState)
          - onAttach(Activity activity) + onAttach(Activity activity)
          - onCreate(Bundle savedInstanceState) + onCreate(Bundle savedInstanceState)
          - onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
          - onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) + onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState)
          Parse attributes during inflation from a view hierarchy into the arguments we handle.
          @@ -1106,7 +1113,7 @@ Summary: void
          - onSaveInstanceState(Bundle outState) + onSaveInstanceState(Bundle outState)
          - setArguments(Bundle args) + setArguments(Bundle args)
          - dump(String arg0, FileDescriptor arg1, PrintWriter arg2, String[] arg3) + dump(String arg0, FileDescriptor arg1, PrintWriter arg2, String[] arg3)
          - equals(Object arg0) + equals(Object arg0)
          getArguments() @@ -1281,10 +1288,10 @@ From class - LayoutInflater + LayoutInflater - getLayoutInflater(Bundle arg0) + getLayoutInflater(Bundle arg0)
          getResources() @@ -1361,7 +1368,7 @@ From class final - String + String getString(int arg0) @@ -1377,10 +1384,10 @@ From class final - String + String - getString(int arg0, Object... arg1) + getString(int arg0, Object... arg1)
          getTag() @@ -1441,7 +1448,7 @@ From class final - CharSequence + CharSequence getText(int arg0) @@ -1473,7 +1480,7 @@ From class - View + View getView() @@ -1524,7 +1531,7 @@ From class Fragment - instantiate(Context arg0, String arg1) + instantiate(Context arg0, String arg1)
          - instantiate(Context arg0, String arg1, Bundle arg2) + instantiate(Context arg0, String arg1, Bundle arg2)
          - onActivityCreated(Bundle arg0) + onActivityCreated(Bundle arg0)
          - onActivityResult(int arg0, int arg1, Intent arg2) + onActivityResult(int arg0, int arg1, Intent arg2)
          - onAttach(Activity arg0) + onAttach(Activity arg0)
          - onConfigurationChanged(Configuration arg0) + onConfigurationChanged(Configuration arg0)
          - onContextItemSelected(MenuItem arg0) + onContextItemSelected(MenuItem arg0)
          - onCreate(Bundle arg0) + onCreate(Bundle arg0)
          onCreateAnimation(int arg0, boolean arg1, int arg2) @@ -1796,7 +1803,7 @@ From class void - onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2) + onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2)
          - onCreateOptionsMenu(Menu arg0, MenuInflater arg1) + onCreateOptionsMenu(Menu arg0, MenuInflater arg1)
          - onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2) + onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2)
          - onInflate(Activity arg0, AttributeSet arg1, Bundle arg2) + onInflate(Activity arg0, AttributeSet arg1, Bundle arg2)
          - onOptionsItemSelected(MenuItem arg0) + onOptionsItemSelected(MenuItem arg0)
          - onOptionsMenuClosed(Menu arg0) + onOptionsMenuClosed(Menu arg0)
          - onPrepareOptionsMenu(Menu arg0) + onPrepareOptionsMenu(Menu arg0)
          - onSaveInstanceState(Bundle arg0) + onSaveInstanceState(Bundle arg0)
          - onViewCreated(View arg0, Bundle arg1) + onViewCreated(View arg0, Bundle arg1)
          - onViewStateRestored(Bundle arg0) + onViewStateRestored(Bundle arg0)
          - registerForContextMenu(View arg0) + registerForContextMenu(View arg0)
          - setArguments(Bundle arg0) + setArguments(Bundle arg0)
          - startActivity(Intent arg0) + startActivity(Intent arg0)
          - startActivityForResult(Intent arg0, int arg1) + startActivityForResult(Intent arg0, int arg1)
          toString() @@ -2292,7 +2299,7 @@ From class void - unregisterForContextMenu(View arg0) + unregisterForContextMenu(View arg0)
          clone() @@ -2350,7 +2357,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -2443,7 +2450,7 @@ From class - String + String toString() @@ -2514,7 +2521,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.content.ComponentCallbacks + android.content.ComponentCallbacks
          - onConfigurationChanged(Configuration arg0) + onConfigurationChanged(Configuration arg0)
          - onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2) + onCreateContextMenu(ContextMenu arg0, View arg1, ContextMenu.ContextMenuInfo arg2)
          java.lang.Objectjava.lang.Object
          clone() @@ -1114,7 +1121,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1207,7 +1214,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptor.html b/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptor.html index f96147ce751752a4841b1376dc1b3227a8c441fc..7f3c1e6579467cb415497a43e94ea125eb78fc59 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptor.html +++ b/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptor.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + BitmapDescriptor | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          clone() @@ -843,7 +850,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -936,7 +943,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html b/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html index 213428cd22b94d2f62d1a0bbe5dbb9e91311045f..226c8d342d858f29d5f0cb46483534152c82b639 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html +++ b/docs/html/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + BitmapDescriptorFactory | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - fromAsset(String assetName) + fromAsset(String assetName)
          Creates a BitmapDescriptor using the name of an image in the assets directory.
          @@ -941,7 +948,7 @@ Summary: BitmapDescriptor
          - fromBitmap(Bitmap image) + fromBitmap(Bitmap image)
          Creates a bitmap descriptor from a given image.
          @@ -959,7 +966,7 @@ Summary: BitmapDescriptor
          - fromFile(String fileName) + fromFile(String fileName)
          Creates a BitmapDescriptor using the name of an image file located in the internal storage.
          @@ -978,7 +985,7 @@ Summary: BitmapDescriptor
          - fromPath(String absolutePath) + fromPath(String absolutePath)
          Creates a bitmap descriptor from an absolute file path.
          @@ -1026,7 +1033,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1064,7 +1071,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1157,7 +1164,7 @@ From class - String + String toString() @@ -1720,7 +1727,7 @@ From class BitmapDescriptor fromAsset - (String assetName) + (String assetName)
          @@ -1764,7 +1771,7 @@ From class BitmapDescriptor fromBitmap - (Bitmap image) + (Bitmap image)
          @@ -1794,7 +1801,7 @@ From class BitmapDescriptor fromFile - (String fileName) + (String fileName)
          @@ -1823,7 +1830,7 @@ From class
          @@ -1845,7 +1852,7 @@ From class BitmapDescriptor fromPath - (String absolutePath) + (String absolutePath)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.Builder.html b/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.Builder.html index d54bcf69f618271e9b617289bee952a4e61fe5cc..7b9940b51250440463260ccf6f1292a73d56cc58 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.Builder.html +++ b/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + CameraPosition.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          clone() @@ -978,7 +985,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1071,7 +1078,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.html b/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.html index b96d938584a11cab4ab6ef96be8ff701339dbd60..e9a513bb2040b10099a1fcdda293e3f0960e7637 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.html +++ b/docs/html/reference/com/google/android/gms/maps/model/CameraPosition.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + CameraPosition | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - createFromAttributes(Context context, AttributeSet attrs) + createFromAttributes(Context context, AttributeSet attrs)
          Creates a CameraPostion from the attribute set
          @@ -1056,7 +1063,7 @@ android.os.Parcelable boolean
          - equals(Object o) + equals(Object o)
          toString() @@ -1122,7 +1129,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1188,7 +1195,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1281,7 +1288,7 @@ From class - String + String toString() @@ -1352,7 +1359,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          - - @@ -1745,7 +1752,7 @@ From interface CameraPositioncreateFromAttributes - (Context context, AttributeSet attrs) + (Context context, AttributeSet attrs)
          @@ -1804,7 +1811,7 @@ From interface boolean equals - (Object o) + (Object o)
          @@ -1907,7 +1914,7 @@ From interface - String + String toString () @@ -1939,7 +1946,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/Circle.html b/docs/html/reference/com/google/android/gms/maps/model/Circle.html index 1c99a241515141e110b4796972d9d96a9d1e5210..1bd7cf0ff02bc270390cb36c1d05d2fa0ee59cf6 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/Circle.html +++ b/docs/html/reference/com/google/android/gms/maps/model/Circle.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Circle | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - + @@ -750,11 +757,11 @@ Summary: The default zIndex is 0.
          Visibility
          Indicates if the circle is visible or invisible, i.e., whether it is drawn on the map. An - invisible polygon is not drawn, but retains all of its other properties. The default is + invisible circle is not drawn, but retains all of its other properties. The default is true, i.e., visible.
          -

          Methods that modify a Polygon must be called on the main thread. If not, an +

          Methods that modify a Circle must be called on the main thread. If not, an IllegalStateException will be thrown at runtime.

          Example

          @@ -882,12 +889,12 @@ Summary: - String + String @@ -1149,7 +1156,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1216,7 +1223,7 @@ From class final - Class<?> + Class<?> + + + + + + + + + + +
          NullPointerException + NullPointerException if target is null
          IllegalArgumentException + IllegalArgumentException if tilt is outside the range of 0 degress inclusive to 90 degrees inclusive.
          java.lang.Objectjava.lang.Object
          getId() -
          Returns this circle's id.
          +
          Gets this circle's id.
          clone() @@ -1187,7 +1194,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1280,7 +1287,7 @@ From class - String + String toString() @@ -1462,7 +1469,7 @@ From class - String + String getId () @@ -1475,7 +1482,7 @@ From class
          -

          Returns this circle's id. +

          Gets this circle's id. The id will be unique amongst all Circles on a map.

          @@ -1721,7 +1728,7 @@ From class
          Throws
          - @@ -1814,7 +1821,7 @@ From class
          Throws
          NullPointerException + NullPointerException if center is null
          - @@ -1909,7 +1916,7 @@ From class
          Throws
          IllegalArgumentException + IllegalArgumentException if radius is negative
          - diff --git a/docs/html/reference/com/google/android/gms/maps/model/CircleOptions.html b/docs/html/reference/com/google/android/gms/maps/model/CircleOptions.html index 0fde34b55210ca8b2c5c191f73b689bab8c45bdf..8e8dfbd8168aaa511f3c36426ce2f1ccceecc4db 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/CircleOptions.html +++ b/docs/html/reference/com/google/android/gms/maps/model/CircleOptions.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +CircleOptions | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -1181,7 +1188,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1248,7 +1255,7 @@ From class final - Class<?> + Class<?> diff --git a/docs/html/reference/com/google/android/gms/maps/model/GroundOverlay.html b/docs/html/reference/com/google/android/gms/maps/model/GroundOverlay.html index 9e0556e2337cd5a776c5c25c3072cfb0dec031e2..2f1af322d34a8dbe6eaf26066ae43f6e4b8dd8ce 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/GroundOverlay.html +++ b/docs/html/reference/com/google/android/gms/maps/model/GroundOverlay.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +GroundOverlay | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -849,7 +856,7 @@ Summary: boolean @@ -916,7 +923,7 @@ Summary: - String + String @@ -1285,7 +1292,7 @@ From class final - Class<?> + Class<?> - + @@ -1289,7 +1296,7 @@ android.os.Parcelable void @@ -1335,7 +1342,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1402,7 +1409,7 @@ From class final - Class<?> + Class<?> @@ -2365,15 +2372,15 @@ From interface
          Throws
          IllegalArgumentException + IllegalArgumentException if width is negative
          java.lang.Objectjava.lang.Object
          clone() @@ -1219,7 +1226,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1312,7 +1319,7 @@ From class - String + String toString() @@ -1383,7 +1390,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          java.lang.Objectjava.lang.Object
          - equals(Object other) + equals(Object other)
          getId() @@ -1218,7 +1225,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1256,7 +1263,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1349,7 +1356,7 @@ From class - String + String toString() @@ -1466,7 +1473,7 @@ From class boolean equals - (Object other) + (Object other)
          @@ -1594,7 +1601,7 @@ From class - String + String getId () @@ -1607,12 +1614,7 @@ From class
          -

          Gets this ground overlay's id. -

          - When a map is restored from a Bundle, ground overlays that were on that - map are also restored. However, those ground overlays will then be represented by different - GroundOverlay objects. A ground overlay's id can be used to retrieve the new - instance of a GroundOverlay object after such restoration.

          +

          Gets this ground overlay's id. The id will be unique amongst all GroundOverlays on a map.

          Returns
          • this ground overlay's id. diff --git a/docs/html/reference/com/google/android/gms/maps/model/GroundOverlayOptions.html b/docs/html/reference/com/google/android/gms/maps/model/GroundOverlayOptions.html index 25209e9c2efd28f71656cdc24623af1787c65742..afadaf9f772045fa05d16b2868f0314bc49bb689 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/GroundOverlayOptions.html +++ b/docs/html/reference/com/google/android/gms/maps/model/GroundOverlayOptions.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + GroundOverlayOptions | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
          • Google Services
          • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1373,7 +1380,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1466,7 +1473,7 @@ From class - String + String toString() @@ -1537,7 +1544,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          - - - @@ -2437,15 +2444,15 @@ From interface
          Throws
          IllegalArgumentException + IllegalArgumentException if anchor is null
          IllegalArgumentException + IllegalArgumentException if width or height are negative
          IllegalStateException + IllegalStateException if the position was already set using positionFromBounds(LatLngBounds)
          - - - @@ -2499,7 +2506,7 @@ From interface
          Throws
          IllegalArgumentException + IllegalArgumentException if anchor is null
          IllegalArgumentException + IllegalArgumentException if width is negative
          IllegalStateException + IllegalStateException if the position was already set using positionFromBounds(LatLngBounds)
          - @@ -2555,7 +2562,7 @@ From interface
          Throws
          IllegalStateException + IllegalStateException if the position was already set using position(LatLng, float) or position(LatLng, float, float)
          - @@ -2613,7 +2620,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/LatLng.html b/docs/html/reference/com/google/android/gms/maps/model/LatLng.html index 0fbd2d2bcf0fea0edd84270f850ddb8e462d5023..f01bba5cd7a878e31f88440ef46db0b32986a417 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/LatLng.html +++ b/docs/html/reference/com/google/android/gms/maps/model/LatLng.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LatLng | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -949,7 +956,7 @@ android.os.Parcelable boolean @@ -1027,7 +1034,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1094,7 +1101,7 @@ From class final - Class<?> + Class<?> @@ -1505,7 +1512,7 @@ From interface boolean equals - (Object o) + (Object o)
          @@ -1565,7 +1572,7 @@ From interface - String + String toString () @@ -1597,7 +1604,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.Builder.html b/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.Builder.html index eafb4d99a9f2711a078e7e6e0745db8cb3e4a05b..cffce26e9b114cd167c7b4d43cd7035ce4f5fd3e 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.Builder.html +++ b/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + LatLngBounds.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - + @@ -868,7 +875,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -935,7 +942,7 @@ From class final - Class<?> + Class<?> - + @@ -842,21 +849,6 @@ Summary: - interface - - - - - - - @@ -882,9 +874,9 @@ Summary: - + - + @@ -973,7 +965,7 @@ Summary: - String + String - - - - - - - - - - - - @@ -1223,7 +1170,7 @@ Summary: void - - - - - - + + + + + + @@ -1465,7 +1394,7 @@ From class final - Class<?> + Class<?> - + @@ -784,7 +805,17 @@ Summary: - + + + + + + + + + + + @@ -792,9 +823,11 @@ Summary: + + - + @@ -804,6 +837,8 @@ Summary: + + @@ -867,6 +902,20 @@ Summary: + class + + + + + + + @@ -914,27 +963,34 @@ Summary: + + + + + + + - + - + - + @@ -954,6 +1010,8 @@ Summary:
          Inherited Constants
          + + - + @@ -1470,6 +1528,8 @@ android.view.View + + + Property<View, Float> @@ -1811,7 +1871,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1822,7 +1882,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1833,7 +1893,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1844,7 +1904,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1855,7 +1915,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1888,7 +1948,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1899,7 +1959,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1921,7 +1981,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1932,7 +1992,7 @@ android.view.View public static final - Property<ViewFloat> + Property<View, Float> @@ -1961,9 +2021,353 @@ android.view.View - -
          IllegalArgumentException + IllegalArgumentException if the transparency is outside the range [0..1].
          java.lang.Objectjava.lang.Object
          - equals(Object o) + equals(Object o)
          Tests if this LatLng is equal to another.
          @@ -980,7 +987,7 @@ android.os.Parcelable - String + String
          toString() @@ -999,7 +1006,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1065,7 +1072,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1158,7 +1165,7 @@ From class - String + String toString() @@ -1229,7 +1236,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          java.lang.Objectjava.lang.Object
          clone() @@ -906,7 +913,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -999,7 +1006,7 @@ From class - String + String toString() @@ -1167,7 +1174,7 @@ From class
          Throws
          - @@ -1205,10 +1212,10 @@ From class way to include this point.

          More precisely, it will consider extending the bounds both in the eastward and westward - directions (one of which may wrap around the world) and choose the smaller of the two. In - the case that both directions result in a LatLngBounds of the same size, this will extend - it in the eastward direction. For example, adding points (0, -179) and (1, 179) will - create a bound crossing the 180 longitude.

          + directions (one of which may cross the antimeridian) and choose the smaller of the two. + In the case that both directions result in a LatLngBounds of the same size, this will + extend it in the eastward direction. For example, adding points (0, -179) and (1, 179) + will create a bound crossing the 180 longitude.

          Parameters
          IllegalStateException + IllegalStateException if no points have been included.
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.html b/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.html index 5322c9166ae5d12aeb2ee3e6f5fa794bb8e80fd3..f8c17515e1e85a5689bc8f9fc2cee3cda812c624 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.html +++ b/docs/html/reference/com/google/android/gms/maps/model/LatLngBounds.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +LatLngBounds | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -1008,7 +1015,7 @@ android.os.Parcelable boolean @@ -1021,6 +1028,24 @@ android.os.Parcelable + LatLng + + + + + + + + + + @@ -1103,7 +1128,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1170,7 +1195,7 @@ From class final - Class<?> + Class<?> @@ -1529,7 +1554,7 @@ From interface
          Throws
          java.lang.Objectjava.lang.Object
          - equals(Object o) + equals(Object o)
          + getCenter() + +
          Returns the center of this LatLngBounds.
          + +
          + + + + + int @@ -1030,7 +1055,7 @@ android.os.Parcelable -
          @@ -1049,14 +1074,14 @@ android.os.Parcelable -
          - String + String toString() @@ -1065,7 +1090,7 @@ android.os.Parcelable -
          @@ -1075,7 +1100,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1141,7 +1166,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1234,7 +1259,7 @@ From class - String + String toString() @@ -1305,7 +1330,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          - @@ -1670,7 +1695,7 @@ From interface boolean equals - (Object o) + (Object o)
          @@ -1686,6 +1711,43 @@ From interface
          + + +
          +

          + + public + + + + + LatLng + + getCenter + () +

          +
          +
          + + + +
          +
          + +

          Returns the center of this LatLngBounds. The center is simply the average of the coordinates + (taking into account if it crosses the antimeridian). This is approximately the geographical + center (it would be exact if the Earth were a perfect sphere). It will not necessarily be + the center of the rectangle as drawn on the map due to the Mercator projection.

          +
          +
          Returns
          +
          • A LatLng that is the center of the LatLngBounds. +
          +
          + +
          +
          + +
          @@ -1743,9 +1805,9 @@ From interface extra point.

          In particular, it will consider extending the bounds both in the eastward and westward - directions (one of which may wrap around the world) and choose the smaller of the two. In the - case that both directions result in a LatLngBounds of the same size, this will extend it in - the eastward direction.

          + directions (one of which may cross the antimeridian) and choose the smaller of the two. In + the case that both directions result in a LatLngBounds of the same size, this will extend it + in the eastward direction.

          Parameters
          IllegalArgumentException + IllegalArgumentException if the latitude of the northeast corner is below the latitude of the southwest corner.
          @@ -1775,7 +1837,7 @@ From interface - String + String toString() @@ -1807,7 +1869,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/Marker.html b/docs/html/reference/com/google/android/gms/maps/model/Marker.html index c5766ca3e6ad069bf8b0c4b5e303b57cbf2071f0..53503fb59830ee896719d02f69b8b1ba340dc4ce 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/Marker.html +++ b/docs/html/reference/com/google/android/gms/maps/model/Marker.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Marker | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -750,6 +757,18 @@ Summary:
          Visibility
          By default, the marker is visible. To make the marker invisible, set this property to false. You can change this value at any time.
          +
          Flat or Billboard
          +
          If the marker is flat against the map, it will remain stuck to the map as the camera rotates + and tilts but will still remain the same size as the camera zooms, unlike a + GroundOverlay. If the marker is a billboard, it will always be drawn facing the camera + and will rotate and tilt with the camera. The default is a billboard (false)
          +
          Rotation
          +
          The rotation of the marker in degrees clockwise about the marker's anchor point. The + axis of rotation is perpendicular to the marker. A rotation of 0 corresponds to the default + position of the marker. When the marker is flat on the map, the default position is North + aligned and the rotation is such that the marker always remains flat on the map. When the + marker is a billboard, the default position is pointing up and the rotation is such that the + marker is always facing the camera. The default value is 0.

          Example

          @@ -836,7 +855,7 @@ Summary: boolean @@ -849,7 +868,7 @@ Summary: - String + String + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1118,9 +1211,9 @@ Summary: void @@ -1133,6 +1226,24 @@ Summary: + void + + + + + + + + @@ -1251,7 +1362,7 @@ From class final - Class<?> + Class<?> - + @@ -848,7 +855,7 @@ Summary: boolean @@ -879,7 +886,7 @@ Summary: - List<List<LatLng>> + List<List<LatLng>> @@ -1283,7 +1290,7 @@ From class final - Class<?> + Class<?> - + @@ -953,7 +960,7 @@ android.os.Parcelable PolygonOptions @@ -1285,7 +1292,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1352,7 +1359,7 @@ From class final - Class<?> + Class<?> @@ -1731,7 +1738,7 @@ From interface PolygonOptionsaddAll - (Iterable<LatLng> points) + (Iterable<LatLng> points)
          @@ -1765,7 +1772,7 @@ From interface PolygonOptions addHole - (Iterable<LatLng> points) + (Iterable<LatLng> points)
          @@ -1929,7 +1936,7 @@ From interface - List<List<LatLng>> + List<List<LatLng>> getHoles () @@ -1963,7 +1970,7 @@ From interface - List<LatLng> + List<LatLng> getPoints () @@ -2274,7 +2281,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/Polyline.html b/docs/html/reference/com/google/android/gms/maps/model/Polyline.html index 8e02c7311e38ef440a7b27d4e295872f322bf8b9..8d7f6ffe98629380a8dc64e4d4387bacf858e92f 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/Polyline.html +++ b/docs/html/reference/com/google/android/gms/maps/model/Polyline.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Polyline | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - + @@ -867,7 +874,7 @@ Summary: boolean @@ -898,7 +905,7 @@ Summary: - String + String @@ -1230,7 +1237,7 @@ From class final - Class<?> + Class<?> - + @@ -951,7 +958,7 @@ android.os.Parcelable PolylineOptions @@ -1211,7 +1218,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1278,7 +1285,7 @@ From class final - Class<?> + Class<?> @@ -1656,7 +1663,7 @@ From interface PolylineOptionsaddAll - (Iterable<LatLng> points) + (Iterable<LatLng> points)
          @@ -1820,7 +1827,7 @@ From interface - List<LatLng> + List<LatLng> getPoints () @@ -2062,7 +2069,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/RuntimeRemoteException.html b/docs/html/reference/com/google/android/gms/maps/model/RuntimeRemoteException.html index 21a1a9707c2ec3c086f3da24cb03eb83741ed5cc..36a045ac07fed116558a505a629cc8c6fa397c23 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/RuntimeRemoteException.html +++ b/docs/html/reference/com/google/android/gms/maps/model/RuntimeRemoteException.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + RuntimeRemoteException | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - + @@ -740,7 +747,7 @@ Summary: - + @@ -750,7 +757,7 @@ Summary: - + @@ -762,7 +769,7 @@ Summary: - + @@ -860,7 +867,7 @@ Summary: @@ -896,7 +903,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Throwable + java.lang.Throwable
          Throwable + Throwable
          @@ -1014,7 +1021,7 @@ From class void @@ -1030,7 +1037,7 @@ From class void @@ -1062,7 +1069,7 @@ From class void @@ -1075,7 +1082,7 @@ From class - String + String @@ -1165,7 +1172,7 @@ From class final - Class<?> + Class<?> - + @@ -960,7 +967,7 @@ android.os.Parcelable void @@ -988,7 +995,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1055,7 +1062,7 @@ From class final - Class<?> + Class<?> @@ -1499,7 +1506,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/TileOverlay.html b/docs/html/reference/com/google/android/gms/maps/model/TileOverlay.html index 738bf5eb29db7a7cd987258a2ccfb338924a3bd3..255e74f85cef430202714648194f5f89102fbece 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/TileOverlay.html +++ b/docs/html/reference/com/google/android/gms/maps/model/TileOverlay.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + TileOverlay | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -869,7 +876,7 @@ Summary: boolean @@ -882,7 +889,7 @@ Summary: - String + String @@ -1088,7 +1095,7 @@ From class final - Class<?> + Class<?> - + @@ -1017,7 +1024,7 @@ android.os.Parcelable void @@ -1064,7 +1071,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1131,7 +1138,7 @@ From class final - Class<?> + Class<?> @@ -1649,7 +1656,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/TileProvider.html b/docs/html/reference/com/google/android/gms/maps/model/TileProvider.html index ac01ca33144ebb25513268035fd9cb8948ddf02d..44a84ca52360b52d43e900b68a83117837855a5e 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/TileProvider.html +++ b/docs/html/reference/com/google/android/gms/maps/model/TileProvider.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + TileProvider | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -903,7 +910,7 @@ Summary: - URL + URL @@ -1003,7 +1010,7 @@ From class final - Class<?> + Class<?> - + @@ -985,7 +992,7 @@ android.os.Parcelable boolean @@ -1063,7 +1070,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1130,7 +1137,7 @@ From class final - Class<?> + Class<?> @@ -1651,7 +1658,7 @@ From interface boolean equals - (Object o) + (Object o)
          @@ -1720,7 +1727,7 @@ From interface - String + String toString () @@ -1752,7 +1759,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/package-summary.html b/docs/html/reference/com/google/android/gms/maps/model/package-summary.html index 2c44fc78b2f85b74f06b6d33ff117c8872b5873d..519140b3159290b909c242ee6d8cd06fef558214 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/package-summary.html +++ b/docs/html/reference/com/google/android/gms/maps/model/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + com.google.android.gms.maps.model | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -841,7 +848,7 @@ Summary: @@ -1171,7 +1178,7 @@ From class final - Class<?> + Class<?> - + @@ -792,7 +799,7 @@ Summary: @@ -895,9 +902,27 @@ Summary: PlusClient.Builder + + + + + + @@ -925,7 +950,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -992,7 +1017,7 @@ From class final - Class<?> + Class<?> @@ -821,7 +828,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onMomentsLoaded - (ConnectionResult status, MomentBuffer momentBuffer, String nextPageToken, String updated) + (ConnectionResult status, MomentBuffer momentBuffer, String nextPageToken, String updated)
          diff --git a/docs/html/reference/com/google/android/gms/plus/PlusClient.OnPeopleLoadedListener.html b/docs/html/reference/com/google/android/gms/plus/PlusClient.OnPeopleLoadedListener.html index dd35c1292ba4b4b6e359a6a0f01fb8f5adba76f3..958049b758dc24f6a3fabfa31cdfa2c2bff1f832 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusClient.OnPeopleLoadedListener.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusClient.OnPeopleLoadedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusClient.OnPeopleLoadedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging @@ -823,7 +830,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPeopleLoaded - (ConnectionResult status, PersonBuffer personBuffer, String nextPageToken) + (ConnectionResult status, PersonBuffer personBuffer, String nextPageToken)
          @@ -839,7 +846,7 @@ onkeyup="return search_changed(event, false, '/')" />
          java.lang.Objectjava.lang.Object
          - equals(Object other) + equals(Object other)
          getId() @@ -885,7 +904,25 @@ Summary: - String + float + + getRotation() + +
          Gets the rotation of the marker.
          + +
          + + + + + + String getSnippet() @@ -896,14 +933,14 @@ Summary: -
          - String + String getTitle() @@ -914,7 +951,7 @@ Summary: -
          @@ -930,7 +967,7 @@ Summary: -
          @@ -948,7 +985,7 @@ Summary: -
          @@ -966,6 +1003,24 @@ Summary: +
          + + + + + + boolean + + isFlat() + +
          Gets the flat setting of the Marker.
          + +
          @@ -1061,6 +1116,25 @@ Summary: + void + + setFlat(boolean flat) + +
          Sets whether this marker should be flat against the map true or a billboard facing + the camera false.
          + +
          + + + + + void @@ -1072,6 +1146,25 @@ Summary: +
          + + + + + + void + + setInfoWindowAnchor(float anchorU, float anchorV) + +
          Specifies the point in the marker image at which to anchor the info window when it is + displayed.
          + +
          @@ -1100,9 +1193,9 @@ Summary: void - setSnippet(String snippet) + setRotation(float rotation) -
          Sets the snippet of the marker.
          +
          Sets the rotation of the marker in degrees clockwise about the marker's anchor point.
          - setTitle(String title) + setSnippet(String snippet) -
          Sets the title of the marker.
          +
          Sets the snippet of the marker.
          + setTitle(String title) + +
          Sets the title of the marker.
          + +
          + + + + + void @@ -1144,7 +1255,7 @@ Summary: -
          @@ -1184,7 +1295,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1222,7 +1333,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1315,7 +1426,7 @@ From class - String + String toString() @@ -1432,7 +1543,7 @@ From class boolean equals - (Object other) + (Object other)
          @@ -1458,7 +1569,7 @@ From class - String + String getId () @@ -1471,12 +1582,7 @@ From class
          -

          Gets this marker's id. -

          - When a map is restored from a Bundle, markers that were on that map are - also restored. However, those markers will then be represented by different Marker - objects. A marker's id can be used to retrieve the new instance of a Marker object after - such restoration.

          +

          Gets this marker's id. The id will be unique amongst all Markers on a map.

          Returns
          • this marker's id. @@ -1521,6 +1627,40 @@ From class
          + + +
          +

          + + public + + + + + float + + getRotation + () +

          +
          +
          + + + +
          +
          + +

          Gets the rotation of the marker.

          +
          +
          Returns
          +
          • the rotation of the marker in degrees clockwise from the default position. +
          +
          + +
          +
          + +
          @@ -1531,7 +1671,7 @@ From class - String + String getSnippet () @@ -1565,7 +1705,7 @@ From class - String + String getTitle () @@ -1685,6 +1825,41 @@ From class
          + + +
          +

          + + public + + + + + boolean + + isFlat + () +

          +
          +
          + + + +
          +
          + +

          Gets the flat setting of the Marker.

          +
          +
          Returns
          +
          • true if the marker is flat against the map; false if the marker + should face the camera. +
          +
          + +
          +
          + +
          @@ -1879,6 +2054,37 @@ From class
          + + +
          +

          + + public + + + + + void + + setFlat + (boolean flat) +

          +
          +
          + + + +
          +
          + +

          Sets whether this marker should be flat against the map true or a billboard facing + the camera false. +

          + +
          +
          + +
          @@ -1918,6 +2124,54 @@ From class
          + + +
          +

          + + public + + + + + void + + setInfoWindowAnchor + (float anchorU, float anchorV) +

          +
          +
          + + + +
          +
          + +

          Specifies the point in the marker image at which to anchor the info window when it is + displayed. This is specified in the same coordinate system as the anchor. See + setAnchor(float, float) for more details. The default is the top middle of the + image.

          +
          +
          Parameters
          + + + + + + + +
          anchorU + u-coordinate of the info window anchor, as a ratio of the image width (in the + range [0, 1])
          anchorV + v-coordinate of the info window anchor, as a ratio of the image height (in the + range [0, 1]) +
          +
          + +
          +
          + +
          @@ -1948,6 +2202,38 @@ From class
          + + +
          +

          + + public + + + + + void + + setRotation + (float rotation) +

          +
          +
          + + + +
          +
          + +

          Sets the rotation of the marker in degrees clockwise about the marker's anchor point. The + axis of rotation is perpendicular to the marker. A rotation of 0 corresponds to the default + position of the marker. +

          + +
          +
          + +
          @@ -1961,7 +2247,7 @@ From class void setSnippet - (String snippet) + (String snippet)
          @@ -1991,7 +2277,7 @@ From class void setTitle - (String title) + (String title)
          @@ -2067,7 +2353,7 @@ From class
          Throws
          - diff --git a/docs/html/reference/com/google/android/gms/maps/model/MarkerOptions.html b/docs/html/reference/com/google/android/gms/maps/model/MarkerOptions.html index 68b9bb584298bc9ee9fa24e3f534f6c674b9b430..bfbca3f9164774fa5ff1df43ac758dd3dec15c2a 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/MarkerOptions.html +++ b/docs/html/reference/com/google/android/gms/maps/model/MarkerOptions.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + +MarkerOptions | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging - + @@ -967,6 +974,25 @@ android.os.Parcelable + MarkerOptions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1168,9 +1284,9 @@ android.os.Parcelable MarkerOptions @@ -1183,6 +1299,24 @@ android.os.Parcelable + MarkerOptions + + + + + + + + @@ -1232,7 +1366,7 @@ android.os.Parcelable class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          @@ -1299,7 +1433,7 @@ From class final - Class<?> + Class<?> @@ -1734,6 +1868,44 @@ From interface + + +
          +

          + + public + + + + + MarkerOptions + + flat + (boolean flat) +

          +
          +
          + + + +
          +
          + +

          Sets whether this marker should be flat against the map true or a billboard facing + the camera false. If the marker is flat against the map, it will remain stuck to the + map as the camera rotates and tilts but will still remain the same size as the camera zooms, + unlike a GroundOverlay. If the marker is a billboard, it will always be drawn facing + the camera and will rotate and tilt with the camera. The default value is false.

          +
          +
          Returns
          +
          • the object for which the method was called, with the new flat state set. +
          +
          + +
          +
          + +
          @@ -1837,6 +2009,74 @@ From interface
          + + +
          +

          + + public + + + + + float + + getInfoWindowAnchorU + () +

          +
          +
          + + + +
          +
          + +

          Horizontal distance, normalized to [0, 1], of the info window anchor from the left edge.

          +
          +
          Returns
          +
          • the u value of the info window anchor. +
          +
          + +
          +
          + + + + +
          +

          + + public + + + + + float + + getInfoWindowAnchorV + () +

          +
          +
          + + + +
          +
          + +

          Vertical distance, normalized to [0, 1], of the info window anchor from the top edge.

          +
          +
          Returns
          +
          • the v value of the info window anchor. +
          +
          + +
          +
          + +
          @@ -1871,6 +2111,40 @@ From interface
          + + +
          +

          + + public + + + + + float + + getRotation + () +

          +
          +
          + + + +
          +
          + +

          Gets the rotation set for this MarkerOptions object.

          +
          +
          Returns
          +
          • the rotation of the marker in degrees clockwise from the default position. +
          +
          + +
          +
          + +
          @@ -1881,7 +2155,7 @@ From interface - String + String getSnippet () @@ -1915,7 +2189,7 @@ From interface - String + String getTitle () @@ -1982,6 +2256,57 @@ From interface
          + + +
          +

          + + public + + + + + MarkerOptions + + infoWindowAnchor + (float u, float v) +

          +
          +
          + + + +
          +
          + +

          Specifies the anchor point of the info window on the marker image. This is specified in the + same coordinate system as the anchor. See anchor(float, float) for more details. + The default is the top middle of the image.

          +
          +
          Parameters
          +
          IllegalArgumentException + IllegalArgumentException if marker is not on this map
          java.lang.Objectjava.lang.Object
          + flat(boolean flat) + +
          Sets whether this marker should be flat against the map true or a billboard facing + the camera false.
          + +
          + + + + + float @@ -978,7 +1004,7 @@ android.os.Parcelable -
          @@ -996,7 +1022,7 @@ android.os.Parcelable -
          @@ -1014,6 +1040,24 @@ android.os.Parcelable +
          + + + + + + float + + getInfoWindowAnchorU() + +
          Horizontal distance, normalized to [0, 1], of the info window anchor from the left edge.
          + +
          @@ -1021,6 +1065,24 @@ android.os.Parcelable + float + + getInfoWindowAnchorV() + +
          Vertical distance, normalized to [0, 1], of the info window anchor from the top edge.
          + +
          + + + + + LatLng @@ -1032,6 +1094,24 @@ android.os.Parcelable +
          + + + + + + float + + getRotation() + +
          Gets the rotation set for this MarkerOptions object.
          + +
          @@ -1039,7 +1119,7 @@ android.os.Parcelable - String + String getSnippet() @@ -1057,7 +1137,7 @@ android.os.Parcelable - String + String getTitle() @@ -1093,6 +1173,24 @@ android.os.Parcelable + MarkerOptions + + infoWindowAnchor(float u, float v) + +
          Specifies the anchor point of the info window on the marker image.
          + +
          + + + + + boolean @@ -1104,6 +1202,24 @@ android.os.Parcelable +
          + + + + + + boolean + + isFlat() + +
          Gets the flat setting for this MarkerOptions object.
          + +
          @@ -1150,9 +1266,9 @@ android.os.Parcelable MarkerOptions - snippet(String snippet) + rotation(float rotation) -
          Sets the snippet for the marker.
          +
          Sets the rotation of the marker in degrees clockwise about the marker's anchor point.
          - title(String title) + snippet(String snippet) -
          Sets the title for the marker.
          +
          Sets the snippet for the marker.
          + title(String title) + +
          Sets the title for the marker.
          + +
          + + + + + MarkerOptions @@ -1194,7 +1328,7 @@ android.os.Parcelable -
          @@ -1204,7 +1338,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1270,7 +1404,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1363,7 +1497,7 @@ From class - String + String toString() @@ -1434,7 +1568,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          + + + + + + +
          u + u-coordinate of the info window anchor, as a ratio of the image width (in the range + [0, 1])
          v + v-coordinate of the info window anchor, as a ratio of the image height (in the range + [0, 1])
          +
          +
          +
          Returns
          +
          • the object for which the method was called, with the new info window anchor set. +
          +
          + +
          +
          + +
          @@ -2016,6 +2341,41 @@ From interface
          + + +
          +

          + + public + + + + + boolean + + isFlat + () +

          +
          +
          + + + +
          +
          + +

          Gets the flat setting for this MarkerOptions object.

          +
          +
          Returns
          +
          • true if the marker is flat against the map; false if the marker + should face the camera. +
          +
          + +
          +
          + +
          @@ -2084,6 +2444,45 @@ From interface
          + + +
          +

          + + public + + + + + MarkerOptions + + rotation + (float rotation) +

          +
          +
          + + + +
          +
          + +

          Sets the rotation of the marker in degrees clockwise about the marker's anchor point. The + axis of rotation is perpendicular to the marker. A rotation of 0 corresponds to the default + position of the marker. When the marker is flat on the map, the default position is North + aligned and the rotation is such that the marker always remains flat on the map. When the + marker is a billboard, the default position is pointing up and the rotation is such that the + marker is always facing the camera. The default value is 0.

          +
          +
          Returns
          +
          • the object for which the method was called, with the new rotation set. +
          +
          + +
          +
          + +
          @@ -2097,7 +2496,7 @@ From interface MarkerOptions snippet - (String snippet) + (String snippet)
          @@ -2131,7 +2530,7 @@ From interface MarkerOptions title - (String title) + (String title)
          @@ -2199,7 +2598,7 @@ From interface void writeToParcel - (Parcel out, int flags) + (Parcel out, int flags)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/Polygon.html b/docs/html/reference/com/google/android/gms/maps/model/Polygon.html index 03db7aba4ae51e133ff1454443b370aca0968eb3..371d98df788e9f5622319c1c94a531235e2e5c42 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/Polygon.html +++ b/docs/html/reference/com/google/android/gms/maps/model/Polygon.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Polygon | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - equals(Object other) + equals(Object other)
          getHoles() @@ -897,7 +904,7 @@ Summary: - String + String getId() @@ -915,7 +922,7 @@ Summary: - List<LatLng> + List<LatLng> getPoints() @@ -1096,7 +1103,7 @@ Summary: void - setHoles(List<? extends List<LatLng>> holes) + setHoles(List<? extends List<LatLng>> holes)
          Sets the holes of this polygon.
          @@ -1114,7 +1121,7 @@ Summary: void
          - setPoints(List<LatLng> points) + setPoints(List<LatLng> points)
          Sets the points of this polygon.
          @@ -1216,7 +1223,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1254,7 +1261,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1347,7 +1354,7 @@ From class - String + String toString() @@ -1464,7 +1471,7 @@ From class boolean equals - (Object other) + (Object other)
          @@ -1524,7 +1531,7 @@ From class - List<List<LatLng>> + List<List<LatLng>> getHoles () @@ -1556,7 +1563,7 @@ From class - String + String getId () @@ -1569,7 +1576,7 @@ From class
          -

          Gets this polygon's id. +

          Gets this polygon's id. The id will be unique amongst all Polygons on a map.

          @@ -1586,7 +1593,7 @@ From class - List<LatLng> + List<LatLng> getPoints () @@ -1931,7 +1938,7 @@ From class void setHoles - (List<? extends List<LatLng>> holes) + (List<? extends List<LatLng>> holes)
          @@ -1971,7 +1978,7 @@ From class void setPoints - (List<LatLng> points) + (List<LatLng> points)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/PolygonOptions.html b/docs/html/reference/com/google/android/gms/maps/model/PolygonOptions.html index c372f98715031794aa9f6b85fa02ce5d03465c29..dd66db3daf7781017d36a859845a0f2e6c1a0213 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/PolygonOptions.html +++ b/docs/html/reference/com/google/android/gms/maps/model/PolygonOptions.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PolygonOptions | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - addAll(Iterable<LatLng> points) + addAll(Iterable<LatLng> points)
          Adds vertices to the outline of the polygon being built.
          @@ -971,7 +978,7 @@ android.os.Parcelable PolygonOptions
          - addHole(Iterable<LatLng> points) + addHole(Iterable<LatLng> points)
          Adds a hole to the polygon being built.
          @@ -1056,7 +1063,7 @@ android.os.Parcelable - List<List<LatLng>> + List<List<LatLng>>
          getHoles() @@ -1074,7 +1081,7 @@ android.os.Parcelable - List<LatLng> + List<LatLng> getPoints() @@ -1239,7 +1246,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1323,7 +1330,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1416,7 +1423,7 @@ From class - String + String toString() @@ -1487,7 +1494,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          java.lang.Objectjava.lang.Object
          - equals(Object other) + equals(Object other)
          getId() @@ -916,7 +923,7 @@ Summary: - List<LatLng> + List<LatLng> getPoints() @@ -1079,7 +1086,7 @@ Summary: void - setPoints(List<LatLng> points) + setPoints(List<LatLng> points)
          Sets the points of this polyline.
          @@ -1163,7 +1170,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1201,7 +1208,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1294,7 +1301,7 @@ From class - String + String toString() @@ -1447,7 +1454,7 @@ From class boolean equals - (Object other) + (Object other)
          @@ -1507,7 +1514,7 @@ From class - String + String getId () @@ -1520,12 +1527,7 @@ From class
          -

          Gets this polyline's id. -

          - When a map is restored from a Bundle, polylines that were on that map are - also restored. However, those polylines will then be represented by different - Polyline objects. A polyline's id can be used to retrieve the new instance of a - Polyline object after such restoration.

          +

          Gets this polyline's id. The id will be unique amongst all Polylines on a map.

          Returns
          • this polyline's id. @@ -1546,7 +1548,7 @@ From class - List<LatLng> + List<LatLng> getPoints () @@ -1858,7 +1860,7 @@ From class void setPoints - (List<LatLng> points) + (List<LatLng> points)
            diff --git a/docs/html/reference/com/google/android/gms/maps/model/PolylineOptions.html b/docs/html/reference/com/google/android/gms/maps/model/PolylineOptions.html index 87c6794334eccd088124a7c13f4cab92a39562a0..b855f5e1a46d2c2d1591e0e1ecd53ef7dd375071 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/PolylineOptions.html +++ b/docs/html/reference/com/google/android/gms/maps/model/PolylineOptions.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PolylineOptions | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
          • Google Services
          • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - addAll(Iterable<LatLng> points) + addAll(Iterable<LatLng> points)
          Adds vertices to the end of the polyline being built.
          @@ -1036,7 +1043,7 @@ android.os.Parcelable - List<LatLng> + List<LatLng>
          getPoints() @@ -1165,7 +1172,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1249,7 +1256,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1342,7 +1349,7 @@ From class - String + String toString() @@ -1413,7 +1420,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          java.lang.Objectjava.lang.Object
             ↳java.lang.Throwablejava.lang.Throwable
             ↳java.lang.Exceptionjava.lang.Exception
             ↳java.lang.RuntimeExceptionjava.lang.RuntimeException
          - RuntimeRemoteException(RemoteException e) + RuntimeRemoteException(RemoteException e)
          fillInStackTrace() @@ -931,7 +938,7 @@ From class - Throwable + Throwable getCause() @@ -947,7 +954,7 @@ From class - String + String getLocalizedMessage() @@ -963,7 +970,7 @@ From class - String + String getMessage() @@ -979,7 +986,7 @@ From class - StackTraceElement[] + StackTraceElement[] getStackTrace() @@ -995,10 +1002,10 @@ From class - Throwable + Throwable - initCause(Throwable arg0) + initCause(Throwable arg0)
          - printStackTrace(PrintWriter arg0) + printStackTrace(PrintWriter arg0)
          - printStackTrace(PrintStream arg0) + printStackTrace(PrintStream arg0)
          - setStackTrace(StackTraceElement[] arg0) + setStackTrace(StackTraceElement[] arg0)
          toString() @@ -1098,7 +1105,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1136,7 +1143,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1229,7 +1236,7 @@ From class - String + String toString() @@ -1340,7 +1347,7 @@ From class RuntimeRemoteException - (RemoteException e) + (RemoteException e)
          diff --git a/docs/html/reference/com/google/android/gms/maps/model/Tile.html b/docs/html/reference/com/google/android/gms/maps/model/Tile.html index 235f104329c615b220a315f9f838aa98d17cdd66..a8293f88f2c5ed5a05ed570e2d96dbcb458a4947 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/Tile.html +++ b/docs/html/reference/com/google/android/gms/maps/model/Tile.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Tile | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1026,7 +1033,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1119,7 +1126,7 @@ From class - String + String toString() @@ -1190,7 +1197,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          java.lang.Objectjava.lang.Object
          - equals(Object other) + equals(Object other)
          getId() @@ -1021,7 +1028,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1059,7 +1066,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1152,7 +1159,7 @@ From class - String + String toString() @@ -1301,7 +1308,7 @@ From class boolean equals - (Object other) + (Object other)
          @@ -1327,7 +1334,7 @@ From class - String + String getId () diff --git a/docs/html/reference/com/google/android/gms/maps/model/TileOverlayOptions.html b/docs/html/reference/com/google/android/gms/maps/model/TileOverlayOptions.html index ac7481d09648491016ec14b4cd9f5c5cc2cb2810..2978c5ace2704086b8f9c175adb5b6e450676fe1 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/TileOverlayOptions.html +++ b/docs/html/reference/com/google/android/gms/maps/model/TileOverlayOptions.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + TileOverlayOptions | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1102,7 +1109,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1195,7 +1202,7 @@ From class - String + String toString() @@ -1266,7 +1273,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          java.lang.Objectjava.lang.Object
          getTileUrl(int x, int y, int zoom) @@ -936,7 +943,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -974,7 +981,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1067,7 +1074,7 @@ From class - String + String toString() @@ -1333,7 +1340,7 @@ From interface abstract - URL + URL getTileUrl (int x, int y, int zoom) diff --git a/docs/html/reference/com/google/android/gms/maps/model/VisibleRegion.html b/docs/html/reference/com/google/android/gms/maps/model/VisibleRegion.html index 02b695ebdc6b364f05dec0e3c86c43c22e8567a1..b038976f4394a8585a889c5eb4818fc99856dd38 100644 --- a/docs/html/reference/com/google/android/gms/maps/model/VisibleRegion.html +++ b/docs/html/reference/com/google/android/gms/maps/model/VisibleRegion.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + VisibleRegion | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - equals(Object o) + equals(Object o)
          Compares this VisibleRegion to another object.
          @@ -1016,7 +1023,7 @@ android.os.Parcelable - String + String
          toString() @@ -1035,7 +1042,7 @@ android.os.Parcelable void - writeToParcel(Parcel out, int flags) + writeToParcel(Parcel out, int flags)
          clone() @@ -1101,7 +1108,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1194,7 +1201,7 @@ From class - String + String toString() @@ -1265,7 +1272,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.os.Parcelable + android.os.Parcelable
          - writeToParcel(Parcel arg0, int arg1) + writeToParcel(Parcel arg0, int arg1)
          - onPanoramaInfoLoaded(ConnectionResult result, Intent viewerIntent) + onPanoramaInfoLoaded(ConnectionResult result, Intent viewerIntent)
          Called on the main thread when panorama info is loaded.
          @@ -819,7 +826,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPanoramaInfoLoaded - (ConnectionResult result, Intent viewerIntent) + (ConnectionResult result, Intent viewerIntent)
          diff --git a/docs/html/reference/com/google/android/gms/panorama/PanoramaClient.html b/docs/html/reference/com/google/android/gms/panorama/PanoramaClient.html index bbe14fbe91f62505d11f266dc70a2c441cb34eb5..944d3bb82366a07543ba40d613f4c70cbc122c3f 100644 --- a/docs/html/reference/com/google/android/gms/panorama/PanoramaClient.html +++ b/docs/html/reference/com/google/android/gms/panorama/PanoramaClient.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PanoramaClient | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - PanoramaClient(Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + PanoramaClient(Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener)
          Creates a panorama client.
          @@ -983,7 +990,7 @@ Summary: void
          - loadPanoramaInfo(PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri) + loadPanoramaInfo(PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri)
          Loads information about a panorama.
          @@ -1001,7 +1008,7 @@ Summary: void
          - loadPanoramaInfoAndGrantAccess(PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri) + loadPanoramaInfoAndGrantAccess(PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri)
          Loads information about a panorama from a content provider.
          @@ -1104,7 +1111,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1142,7 +1149,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1235,7 +1242,7 @@ From class - String + String toString() @@ -1554,7 +1561,7 @@ From interface PanoramaClient - (Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + (Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener)
          @@ -1838,7 +1845,7 @@ From interface void loadPanoramaInfo - (PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri) + (PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri)
          @@ -1882,7 +1889,7 @@ From interface void loadPanoramaInfoAndGrantAccess - (PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri) + (PanoramaClient.OnPanoramaInfoLoadedListener listener, Uri uri)
          diff --git a/docs/html/reference/com/google/android/gms/panorama/package-summary.html b/docs/html/reference/com/google/android/gms/panorama/package-summary.html index bd15388fea76fb47ee4e224e077ab25a7e7b553e..66f7b45f6464053452b39074470394c1717b073e 100644 --- a/docs/html/reference/com/google/android/gms/panorama/package-summary.html +++ b/docs/html/reference/com/google/android/gms/panorama/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + com.google.android.gms.panorama | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
            +
          • + Overview +
          • Getting Started
          • -
          • - Architectural Overview +
          • + Implementing GCM Client
          • -
          • - Cloud Connection Server +
          • User Notifications
          • -
          • - GCM Client -
          • -
          • - GCM Server -
          • Advanced Topics
          • diff --git a/docs/html/reference/com/google/android/gms/plus/GooglePlusUtil.html b/docs/html/reference/com/google/android/gms/plus/GooglePlusUtil.html deleted file mode 100644 index 541cda4a8ec502d5c484e9e46a7def370e970e1c..0000000000000000000000000000000000000000 --- a/docs/html/reference/com/google/android/gms/plus/GooglePlusUtil.html +++ /dev/null @@ -1,1528 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -GooglePlusUtil | Android Developers - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            - - - - - - - - -
            - -
            - - - - - - - - - - - - -
            - - - - -
            -
            - - - - -
            - public - - - - class -

            GooglePlusUtil

            - - - - - extends Object
            - - - - - - - - - -
            - -
            - -
            - - - - - - - - - - - - - - - - - -
            java.lang.Object
               ↳com.google.android.gms.plus.GooglePlusUtil
            - - - - - - - -
            - - -

            Class Overview

            -

            Utility class for verifying that the Google+ app is available and - up-to-date on this device. -

            - - - - - -
            - - - - - - - - - - - - - - - - -
            - - -

            Summary

            - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            Constants
            intAPP_DISABLEDStatus code indicating the Google+ app is installed, but disabled.
            intAPP_MISSINGStatus code indicating the Google+ app is not installed.
            intAPP_UPDATE_REQUIREDStatus code indicating the Google+ app is installed, but is older than the - version required.
            StringGOOGLE_PLUS_PACKAGEThe package name of the Google+ Android app.
            StringPLATFORM_LOGGING_TAGProperty to enable logging across the Google+ platform for Android.
            intSUCCESSStatus code indicating the Google+ app is installed and up-to-date.
            - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            Public Methods
            - - - - static - - int - - checkGooglePlusApp(Context context) - -
            Checks if the version of the Google+ app installed on this device is up-to-date.
            - -
            - - - - static - - Dialog - - getErrorDialog(int errorCode, Activity activity, int requestCode) - -
            Returns a dialog to address the provided errorCode.
            - -
            - - - - - - - - - - - - - - - -
            - [Expand] -
            Inherited Methods
            - -From class - - java.lang.Object - -
            - - -
            -
            - - -
            - - - - - - - - - - - - - - - - - - - - -

            Constants

            - - - - - - -
            -

            - - public - static - final - int - - APP_DISABLED -

            -
            - - - - -
            -
            - -

            Status code indicating the Google+ app is installed, but disabled. -

            - - -
            - Constant Value: - - - 3 - (0x00000003) - - -
            - -
            -
            - - - - - -
            -

            - - public - static - final - int - - APP_MISSING -

            -
            - - - - -
            -
            - -

            Status code indicating the Google+ app is not installed. -

            - - -
            - Constant Value: - - - 1 - (0x00000001) - - -
            - -
            -
            - - - - - -
            -

            - - public - static - final - int - - APP_UPDATE_REQUIRED -

            -
            - - - - -
            -
            - -

            Status code indicating the Google+ app is installed, but is older than the - version required. -

            - - -
            - Constant Value: - - - 2 - (0x00000002) - - -
            - -
            -
            - - - - - -
            -

            - - public - static - final - String - - GOOGLE_PLUS_PACKAGE -

            -
            - - - - -
            -
            - -

            The package name of the Google+ Android app. -

            - - -
            - Constant Value: - - - "com.google.android.apps.plus" - - -
            - -
            -
            - - - - - -
            -

            - - public - static - final - String - - PLATFORM_LOGGING_TAG -

            -
            - - - - -
            -
            - -

            Property to enable logging across the Google+ platform for Android. -

            - To enable logging:
            - adb shell setprop log.tag.GooglePlusPlatform VERBOSE -

            -

            - To disable logging:
            - adb shell setprop log.tag.GooglePlusPlatform "" -

            -

            - - -
            - Constant Value: - - - "GooglePlusPlatform" - - -
            - -
            -
            - - - - - -
            -

            - - public - static - final - int - - SUCCESS -

            -
            - - - - -
            -
            - -

            Status code indicating the Google+ app is installed and up-to-date. -

            - - -
            - Constant Value: - - - 0 - (0x00000000) - - -
            - -
            -
            - - - - - - - - - - - - - - - - - - - -

            Public Methods

            - - - - - -
            -

            - - public - static - - - - int - - checkGooglePlusApp - (Context context) -

            -
            -
            - - - -
            -
            - -

            Checks if the version of the Google+ app installed on this device is up-to-date.

            -
            -
            Parameters
            - - - - -
            context - The context.
            -
            -
            -
            Returns
            - -
            - -
            -
            - - - - -
            -

            - - public - static - - - - Dialog - - getErrorDialog - (int errorCode, Activity activity, int requestCode) -

            -
            -
            - - - -
            -
            - -

            Returns a dialog to address the provided errorCode. Upon confirmation, the user is directed - to either the Google Play Store or the System App Settings screen to resolve the error.

            -
            -
            Parameters
            - - - - - - - - - - -
            errorCode - error code returned by checkGooglePlusApp(Context) call. If - errorCode is SUCCESS then null is returned.
            activity - parent activity for creating the dialog, also used for identifying language - to display dialog in.
            requestCode - The non-negative request code given when calling - startActivityForResult(Intent, int). -
            -
            - -
            -
            - - - - - - - - - - - - - -
            - -
            - -
            - - - - - - - - diff --git a/docs/html/reference/com/google/android/gms/plus/PlusClient.Builder.html b/docs/html/reference/com/google/android/gms/plus/PlusClient.Builder.html index 63fd80869c1af602a02bece020c74b23c05090aa..03256944aa69e822b159de16f14989b895dd6865 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusClient.Builder.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusClient.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusClient.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
          • Google Services
          • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          - PlusClient.Builder(Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + PlusClient.Builder(Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener)
          Builder to help construct the PlusClient object.
          @@ -859,7 +866,7 @@ Summary: PlusClient.Builder
          - setAccountName(String accountName) + setAccountName(String accountName)
          Specify an account name on the device that should be used.
          @@ -877,9 +884,9 @@ Summary: PlusClient.Builder
          - setScopes(String... scopes) + setActions(String... actions) -
          Specify the OAuth 2.0 scopes requested by your app.
          +
          Specify which user's app activity types can be written to Google.
          - setVisibleActivities(String... visibleActivities) + setScopes(String... scopes) -
          Specify which user's app activity types can be written to Google.
          +
          Specify the OAuth 2.0 scopes requested by your app.
          + +
          + + + + + + PlusClient.Builder + + setVisibleActivities(String... actions) + +
          clone() @@ -963,7 +988,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1056,7 +1081,7 @@ From class - String + String toString() @@ -1165,7 +1190,7 @@ From class PlusClient.Builder - (Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener) + (Context context, GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener connectionFailedListener)
          @@ -1293,7 +1318,7 @@ From class PlusClient.Builder setAccountName - (String accountName) + (String accountName)
          @@ -1322,6 +1347,55 @@ From class
          + + +
          +

          + + public + + + + + PlusClient.Builder + + setActions + (String... actions) +

          +
          +
          + + + +
          +
          + +

          Specify which user's app activity types can be written to Google. + This must be used with the PLUS_LOGIN OAuth 2.0 scope. + +

          + See Types of app + activity for the full list of valid app activity types. Example usage: +

          +      plusClientBuilder.setActions(
          +          "http://schemas.google.com/AddActivity",
          +          "http://schemas.google.com/BuyActivity");
          + 

          +
          +
          Parameters
          + + + + +
          actions + The user's app activity types that can be written to Google. +
          +
          + +
          +
          + +
          @@ -1335,7 +1409,7 @@ From class PlusClient.Builder setScopes - (String... scopes) + (String... scopes)
          @@ -1379,7 +1453,7 @@ From class PlusClient.Builder setVisibleActivities - (String... visibleActivities) + (String... actions)
          @@ -1389,27 +1463,8 @@ From class
          -

          Specify which user's app activity types can be written to Google. - This must be used with the PLUS_LOGIN OAuth 2.0 scope. - -

          - See Types of app - activity for the full list of valid app activity types. Example usage: -

          -      plusClientBuilder.setVisibleActivities(
          -          "http://schemas.google.com/AddActivity",
          -          "http://schemas.google.com/BuyActivity");
          - 

          -
          -
          Parameters
          - - - - -
          visibleActivities - The user's app activity types that can be written to Google. -
          -
          +
          diff --git a/docs/html/reference/com/google/android/gms/plus/PlusClient.OnAccessRevokedListener.html b/docs/html/reference/com/google/android/gms/plus/PlusClient.OnAccessRevokedListener.html index 2d9abf11826bfd367c508f280fc9a8047166ce98..0a5c0eb11ea56af347ca99ce2363061fb851f394 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusClient.OnAccessRevokedListener.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusClient.OnAccessRevokedListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusClient.OnAccessRevokedListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - onMomentsLoaded(ConnectionResult status, MomentBuffer momentBuffer, String nextPageToken, String updated) + onMomentsLoaded(ConnectionResult status, MomentBuffer momentBuffer, String nextPageToken, String updated)
          - onPeopleLoaded(ConnectionResult status, PersonBuffer personBuffer, String nextPageToken) + onPeopleLoaded(ConnectionResult status, PersonBuffer personBuffer, String nextPageToken)
          diff --git a/docs/html/reference/com/google/android/gms/plus/PlusClient.OnPersonLoadedListener.html b/docs/html/reference/com/google/android/gms/plus/PlusClient.OnPersonLoadedListener.html deleted file mode 100644 index 4de4db7ccda2284d5c3d62525f002aaf39466a3b..0000000000000000000000000000000000000000 --- a/docs/html/reference/com/google/android/gms/plus/PlusClient.OnPersonLoadedListener.html +++ /dev/null @@ -1,897 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -PlusClient.OnPersonLoadedListener | Android Developers - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          - - - - - - - - -
          - -
          - - - - - -
          - - - - -
          -
          - - - - -
          - public - static - - - interface -

          PlusClient.OnPersonLoadedListener

          - - - - - - - - -
          - -
          - -
          -
          status - The resulting connection status of the loadPeople(PlusClient.OnPeopleLoadedListener, int) or + The resulting connection status of the loadPeople(PlusClient.OnPeopleLoadedListener, String...) or loadVisiblePeople(PlusClient.OnPeopleLoadedListener, int, String) request.
          - - - - - - - - -
          com.google.android.gms.plus.PlusClient.OnPersonLoadedListener
          - - - - - - - -
          -

          -

          - This interface is deprecated.
          - See PlusClient.OnPeopleLoadedListener. - -

          - - - - - -
          - - - - - - - - - - - - - - - - -
          - - -

          Summary

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          Public Methods
          - abstract - - - - - void - - onPersonLoaded(ConnectionResult status, Person person) - -
          - This method is deprecated. - See onPeopleLoaded(ConnectionResult, PersonBuffer, String). -
          - -
          - - - - - - - -
          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

          Public Methods

          - - - - - -
          -

          - - public - - - abstract - - void - - onPersonLoaded - (ConnectionResult status, Person person) -

          -
          -
          - - - -
          -
          -

          -

          - This method is deprecated.
          - See onPeopleLoaded(ConnectionResult, PersonBuffer, String). - -

          -

          - -
          -
          - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/html/reference/com/google/android/gms/plus/PlusClient.OrderBy.html b/docs/html/reference/com/google/android/gms/plus/PlusClient.OrderBy.html index 43ed0079058ab447b5fc217a878ed8565a25c044..13a0675a15fe8d094dc8e3f6d7ab9dc830cae9c3 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusClient.OrderBy.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusClient.OrderBy.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusClient.OrderBy | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
          PlusClient.OnPersonLoadedListener - This interface is deprecated. - See PlusClient.OnPeopleLoadedListener. - 
          - - - - interface PlusClient.OrderBy Constants to declare the order to return people in. 
          StringString KEY_REQUEST_VISIBLE_ACTIVITIESBundle key for specifying which user's app activity (moment) types can be written to Google.See KEY_REQUEST_ACTIONS.
          getAccountName() @@ -1087,7 +1079,7 @@ Summary: void - loadMoments(PlusClient.OnMomentsLoadedListener listener, int maxResults, String pageToken, Uri targetUrl, String type, String userId) + loadMoments(PlusClient.OnMomentsLoadedListener listener, int maxResults, String pageToken, Uri targetUrl, String type, String userId)
          List all of the moments for a particular user.
          @@ -1123,7 +1115,7 @@ Summary: void
          - loadPeople(PlusClient.OnPeopleLoadedListener listener, Collection<String> personIds) + loadPeople(PlusClient.OnPeopleLoadedListener listener, Collection<String> personIds)
          Loads a list of specified people.
          @@ -1141,7 +1133,7 @@ Summary: void
          - loadPeople(PlusClient.OnPeopleLoadedListener listener, String... personIds) + loadPeople(PlusClient.OnPeopleLoadedListener listener, String... personIds) @@ -1160,54 +1152,9 @@ Summary: void - loadPeople(PlusClient.OnPeopleLoadedListener listener, int collection) + loadVisiblePeople(PlusClient.OnPeopleLoadedListener listener, String pageToken) - - -
          - - - - - - void - - loadPeople(PlusClient.OnPeopleLoadedListener listener, int collection, int orderBy, int maxResults, String pageToken) - - - -
          - - - - - - void - - loadPerson(PlusClient.OnPersonLoadedListener listener, String userId) - - +
          Loads the list of visible people in the user's circles.
          - loadVisiblePeople(PlusClient.OnPeopleLoadedListener listener, String pageToken) + loadVisiblePeople(PlusClient.OnPeopleLoadedListener listener, int orderBy, String pageToken)
          Loads the list of visible people in the user's circles.
          @@ -1238,24 +1185,6 @@ Summary: - void -
          - loadVisiblePeople(PlusClient.OnPeopleLoadedListener listener, int orderBy, String pageToken) - -
          Loads the list of visible people in the user's circles.
          - -
          - - - - - void @@ -1267,7 +1196,7 @@ Summary: -
          @@ -1286,7 +1215,7 @@ Summary: -
          @@ -1296,7 +1225,7 @@ Summary: void - removeMoment(String momentId) + removeMoment(String momentId)
          Delete a moment.
          @@ -1304,7 +1233,7 @@ Summary: -
          @@ -1322,7 +1251,7 @@ Summary: -
          @@ -1340,7 +1269,7 @@ Summary: -
          @@ -1358,7 +1287,7 @@ Summary: -
          @@ -1398,7 +1327,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
          Object + Object
          clone() @@ -1436,7 +1365,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
          getClass() @@ -1529,7 +1458,7 @@ From class - String + String toString() @@ -1838,7 +1767,7 @@ From interface public static final - String + String KEY_REQUEST_VISIBLE_ACTIVITIES @@ -1850,23 +1779,7 @@ From interface
          -

          Bundle key for specifying which user's app activity (moment) types can be written to Google. - The list of activity types are represented as a space-separated string passed in the extras - Bundle when calling getToken(Context, String, String, Bundle). - -

          - This bundle key should be included in the extras Bundle when calling - getToken(Context, String, String, Bundle) and should only be used when - requesting the PLUS_LOGIN OAuth 2.0 scope. - - See Types of moments - for the full list of valid activity types. Example usage: -

          -     Bundle bundle = new Bundle();
          -     bundle.putString(PlusClient.KEY_REQUEST_VISIBLE_ACTIVITIES,
          -              "http://schemas.google.com/AddActivity http://schemas.google.com/BuyActivity");
          -     String token = GoogleAuthUtil.getToken(context, accountName, Scopes.PLUS_LOGIN, bundle);
          - 
          + @@ -2015,7 +1928,7 @@ From interface - String + String getAccountName () @@ -2039,7 +1952,7 @@ From interface
          Throws
          - @@ -2265,7 +2178,7 @@ From interface void loadMoments - (PlusClient.OnMomentsLoadedListener listener, int maxResults, String pageToken, Uri targetUrl, String type, String userId) + (PlusClient.OnMomentsLoadedListener listener, int maxResults, String pageToken, Uri targetUrl, String type, String userId)
          @@ -2380,7 +2293,7 @@ From interface void loadPeople - (PlusClient.OnPeopleLoadedListener listener, Collection<String> personIds) + (PlusClient.OnPeopleLoadedListener listener, Collection<String> personIds)
          @@ -2432,7 +2345,7 @@ From interface void loadPeople - (PlusClient.OnPeopleLoadedListener listener, String... personIds) + (PlusClient.OnPeopleLoadedListener listener, String... personIds)
          @@ -2450,108 +2363,6 @@ From interface
          - - -
          -

          - - public - - - - - void - - loadPeople - (PlusClient.OnPeopleLoadedListener listener, int collection) -

          -
          -
          - - - -
          -
          -

          -

          - This method is deprecated.
          - See loadVisiblePeople(PlusClient.OnPeopleLoadedListener, int, String). - -

          -

          - -
          -
          - - - - -
          -

          - - public - - - - - void - - loadPeople - (PlusClient.OnPeopleLoadedListener listener, int collection, int orderBy, int maxResults, String pageToken) -

          -
          -
          - - - -
          -
          -

          -

          - This method is deprecated.
          - See loadVisiblePeople(PlusClient.OnPeopleLoadedListener, int, String). - -

          -

          - -
          -
          - - - - -
          -

          - - public - - - - - void - - loadPerson - (PlusClient.OnPersonLoadedListener listener, String userId) -

          -
          -
          - - - -
          -
          -

          -

          - This method is deprecated.
          - See loadPeople(PlusClient.OnPeopleLoadedListener, java.util.Collection). - -

          -

          - -
          -
          - -
          @@ -2565,7 +2376,7 @@ From interface void loadVisiblePeople - (PlusClient.OnPeopleLoadedListener listener, String pageToken) + (PlusClient.OnPeopleLoadedListener listener, String pageToken)
          @@ -2581,7 +2392,7 @@ From interface

          Each Person will contain the id, displayName, image, objectType, and url fields populated. - To retrieve additional profile data, use the loadPeople(PlusClient.OnPeopleLoadedListener, int) method. + To retrieve additional profile data, use the loadPeople(PlusClient.OnPeopleLoadedListener, String...) method.

          This method requires the PLUS_LOGIN OAuth 2.0 scope specified in the PlusClient.Builder

          @@ -2614,7 +2425,7 @@ From interface void loadVisiblePeople - (PlusClient.OnPeopleLoadedListener listener, int orderBy, String pageToken) + (PlusClient.OnPeopleLoadedListener listener, int orderBy, String pageToken)
          @@ -2630,7 +2441,7 @@ From interface

          Each Person will contain the id, displayName, image, objectType, and url fields populated. - To retrieve additional profile data, use the loadPeople(PlusClient.OnPeopleLoadedListener, int) method. + To retrieve additional profile data, use the loadPeople(PlusClient.OnPeopleLoadedListener, String...) method.

          This method requires the PLUS_LOGIN OAuth 2.0 scope specified in the PlusClient.Builder

          @@ -2773,7 +2584,7 @@ From interface void removeMoment - (String momentId) + (String momentId)
          diff --git a/docs/html/reference/com/google/android/gms/plus/PlusOneButton.DefaultOnPlusOneClickListener.html b/docs/html/reference/com/google/android/gms/plus/PlusOneButton.DefaultOnPlusOneClickListener.html new file mode 100644 index 0000000000000000000000000000000000000000..a8fb8dc6ca3f527e2d80412ab43e3d7f1f220db6 --- /dev/null +++ b/docs/html/reference/com/google/android/gms/plus/PlusOneButton.DefaultOnPlusOneClickListener.html @@ -0,0 +1,1381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PlusOneButton.DefaultOnPlusOneClickListener | Android Developers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          + + + + + + + + +
          + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + +
          + + + + +
          +
          + + + + +
          + protected + + + + class +

          PlusOneButton.DefaultOnPlusOneClickListener

          + + + + + extends Object
          + + + + + + + implements + + View.OnClickListener + + PlusOneButton.OnPlusOneClickListener + + + + + +
          + +
          + +
          +
          SecurityException + SecurityException If your app doesn't have the GET_ACCOUNTS permission.
          + + + + + + + + + + + + + + + + +
          java.lang.Object
             ↳com.google.android.gms.plus.PlusOneButton.DefaultOnPlusOneClickListener
          + + + + + + + +
          + + +

          Class Overview

          +

          This is an View.OnClickListener that will proxy clicks to an + attached PlusOneButton.OnPlusOneClickListener, or default to attempt to start + the intent using an Activity context. + + Important: The implementation of ERROR(/OnClickListener#onClick(android.view.View)) + used by DefaultOnPlusOneClickListener relies on the tag of this class' + PlusOneButtonView remaining unused. +

          + + + + + +
          + + + + + + + + + + + + + + + + +
          + + +

          Summary

          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          Public Constructors
          + + + + + + + + PlusOneButton.DefaultOnPlusOneClickListener(PlusOneButton.OnPlusOneClickListener proxy) + +
          + + + + + + + + + + + + + + + + + + + + + + + + +
          Public Methods
          + + + + + + void + + onClick(View view) + +
          + + + + + + void + + onPlusOneClick(Intent intent) + +
          Called when the +1 button is clicked.
          + +
          + + + + + + + + + + + + + + + + + + + + + + + + + +
          + [Expand] +
          Inherited Methods
          + +From class + + java.lang.Object + +
          + + +
          +
          + +From interface + + android.view.View.OnClickListener + +
          + + +
          +
          + +From interface + + com.google.android.gms.plus.PlusOneButton.OnPlusOneClickListener + +
          + + +
          +
          + + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + +

          Public Constructors

          + + + + + +
          +

          + + public + + + + + + + PlusOneButton.DefaultOnPlusOneClickListener + (PlusOneButton.OnPlusOneClickListener proxy) +

          +
          +
          + + + +
          +
          + +

          + +
          +
          + + + + + + + + + + + + + +

          Public Methods

          + + + + + +
          +

          + + public + + + + + void + + onClick + (View view) +

          +
          +
          + + + +
          +
          + +

          + +
          +
          + + + + +
          +

          + + public + + + + + void + + onPlusOneClick + (Intent intent) +

          +
          +
          + + + +
          +
          + +

          Called when the +1 button is clicked. Start the intent passed to this method + to display the +1 confirmation dialog Activity with + startActivityForResult(Intent, int).

          +
          +
          Parameters
          + + + + +
          intent + The intent to display the +1 confirmation dialog. +
          +
          + +
          +
          + + + + + + + + + + + + + +
          + +
          + + + + + + + + + + diff --git a/docs/html/reference/com/google/android/gms/plus/PlusOneButton.OnPlusOneClickListener.html b/docs/html/reference/com/google/android/gms/plus/PlusOneButton.OnPlusOneClickListener.html index a6370c5d396b135c26084debc9983f027980e586..03aa27e0a77905065d1acdb53a5afbe35b5b0c6a 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusOneButton.OnPlusOneClickListener.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusOneButton.OnPlusOneClickListener.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusOneButton.OnPlusOneClickListener | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          - onPlusOneClick(Intent intent) + onPlusOneClick(Intent intent)
          Called when the +1 button is clicked.
          @@ -820,7 +879,7 @@ onkeyup="return search_changed(event, false, '/')" /> void onPlusOneClick - (Intent intent) + (Intent intent)
          diff --git a/docs/html/reference/com/google/android/gms/plus/PlusOneButton.html b/docs/html/reference/com/google/android/gms/plus/PlusOneButton.html index 6ee2886017ca301f84cb0575edd8438c6e2f9321..96589fbc9547e3d1ebb075ddbe76ec3747c1cb04 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusOneButton.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusOneButton.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusOneButton | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
        • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
        • Google Services
        • +
          @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
          java.lang.Objectjava.lang.Object
             ↳android.view.Viewandroid.view.View
              ↳android.view.ViewGroup
               ↳android.view.ViewGroupandroid.widget.FrameLayout
               ↳ com.google.android.gms.plus.PlusOneButtonPlusOneButton.DefaultOnPlusOneClickListenerThis is an View.OnClickListener that will proxy clicks to an + attached PlusOneButton.OnPlusOneClickListener, or default to attempt to start + the intent using an Activity context. 
          + + + + interface PlusOneButton.OnPlusOneClickListener A listener for +1 button clicks. 
          intDEFAULT_ACTIVITY_REQUEST_CODEAn empty ActivityRequestCode to serve as the default before the code has been assigned.
          int SIZE_MEDIUM The medium button size.
          int SIZE_SMALL The small button size.
          int SIZE_STANDARD The standard button size.
          int SIZE_TALL The tall button size.
          @@ -1423,7 +1481,7 @@ android.view.View
          StringString VIEW_LOG_TAG
          @@ -1492,7 +1552,7 @@ android.view.View public static final - Property<ViewFloat> ALPHA
          ROTATION
          ROTATION_X
          ROTATION_Y
          SCALE_X
          SCALE_Y
          TRANSLATION_X
          TRANSLATION_Y
          X
          Y
          + +
          Public Constructors
          + + + + + + + + + + + + + + + + +
          Public Constructors
          + + + + + + + + PlusOneButton(Context context) + +
          Creates a +1 button of SIZE_STANDARD size with an + ANNOTATION_BUBBLE annotation.
          + +
          + + + + + + + + PlusOneButton(Context context, AttributeSet attrs) + +
          Creates a +1 button of SIZE_STANDARD size with an + ANNOTATION_BUBBLE annotation.
          + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          Public Methods
          + + + + + + void + + initialize(String url, PlusOneButton.OnPlusOneClickListener plusOneClickListener) + +
          Updates the +1 button with a client and URL.
          + +
          + + + + + + void + + initialize(String url, int activityRequestCode) + +
          Updates the +1 button with a PlusClient and URL.
          + +
          + + + + + + void + + setAnnotation(int annotation) + +
          Sets the annotation to display next to the +1 button.
          + +
          + + + + + + void + + setOnPlusOneClickListener(PlusOneButton.OnPlusOneClickListener listener) + +
          Sets the PlusOneButton.OnPlusOneClickListener to handle clicks.
          + +
          + + + + + + void + + setSize(int size) + +
          Sets the size of the +1 button.
          + +
          + + + + + + + + + + + + + + + + + + + + + + +
          Protected Methods
          + + + + static + + int + + getAnnotation(Context context, AttributeSet attrs) + +
          + + + + static + + int + + getSize(Context context, AttributeSet attrs) + +
          + + + + + + + + + + + + + + + + + + + + + - -
          + [Expand] +
          Inherited Methods
          + +From class + + android.widget.FrameLayout + +
          + +
          + + + + + + boolean + + getMeasureAllChildren() + +
          + + + + + + void + + jumpDrawablesToCurrentState() + +
          + + + + + + void + + onInitializeAccessibilityEvent(AccessibilityEvent arg0) + +
          + + + + @@ -2028,10 +2477,8 @@ android.view.View void @@ -2046,10 +2493,8 @@ android.view.View void @@ -2064,10 +2509,8 @@ android.view.View void @@ -2082,10 +2525,8 @@ android.view.View void @@ -2100,26 +2541,64 @@ android.view.View void - -
          Public Methods
          + + + + + + void + + onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo arg0) + +
          - initialize(PlusClient plusClient, String url, PlusOneButton.OnPlusOneClickListener plusOneClickListener) + onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) -
          Updates the +1 button with a client and URL.
          -
          - initialize(PlusClient plusClient, String url, int activityRequestCode) + onMeasure(int arg0, int arg1) -
          Updates the +1 button with a PlusClient and URL.
          -
          - setAnnotation(int annotation) + onSizeChanged(int arg0, int arg1, int arg2, int arg3) -
          Sets the annotation to display next to the +1 button.
          -
          - setOnPlusOneClickListener(PlusOneButton.OnPlusOneClickListener listener) + setForeground(Drawable arg0) -
          Sets the PlusOneButton.OnPlusOneClickListener to handle clicks.
          -
          - setSize(int size) + setForegroundGravity(int arg0) -
          Sets the size of the +1 button.
          -
          + +
          + + + + + + void + + setMeasureAllChildren(boolean arg0) + +
          + + + + + + boolean + + shouldDelayChildPressedState() + +
          + + + + + + boolean + + verifyDrawable(Drawable arg0) + +
          + + +
        @@ -2130,7 +2609,7 @@ android.view.View class="jd-expando-trigger-img" /> From class - android.view.ViewGroup + android.view.ViewGroup
        @@ -2168,7 +2647,7 @@ From class void @@ -2200,7 +2679,7 @@ From class void @@ -2216,7 +2695,7 @@ From class void @@ -2232,7 +2711,7 @@ From class void @@ -2248,7 +2727,7 @@ From class void @@ -2264,7 +2743,7 @@ From class void @@ -2280,7 +2759,7 @@ From class void @@ -2296,7 +2775,7 @@ From class boolean @@ -2312,7 +2791,7 @@ From class boolean @@ -2328,7 +2807,7 @@ From class void @@ -2344,7 +2823,7 @@ From class void @@ -2360,7 +2839,7 @@ From class void @@ -2392,7 +2871,7 @@ From class boolean @@ -2408,7 +2887,7 @@ From class void @@ -2424,7 +2903,7 @@ From class void @@ -2440,7 +2919,7 @@ From class void @@ -2520,7 +2999,7 @@ From class void @@ -2568,7 +3047,7 @@ From class void @@ -2616,7 +3095,7 @@ From class void @@ -2632,7 +3111,7 @@ From class void @@ -2648,7 +3127,7 @@ From class boolean @@ -2664,7 +3143,7 @@ From class boolean @@ -2680,7 +3159,7 @@ From class boolean @@ -2696,7 +3175,7 @@ From class boolean @@ -2712,7 +3191,7 @@ From class boolean @@ -2728,7 +3207,7 @@ From class boolean @@ -2744,7 +3223,7 @@ From class void @@ -2760,7 +3239,7 @@ From class void @@ -2840,7 +3319,7 @@ From class void @@ -2856,7 +3335,7 @@ From class boolean @@ -2872,7 +3351,7 @@ From class boolean @@ -2888,7 +3367,7 @@ From class boolean @@ -2904,7 +3383,7 @@ From class void @@ -2968,7 +3447,7 @@ From class boolean @@ -3000,7 +3479,7 @@ From class void @@ -3013,7 +3492,7 @@ From class - View + View @@ -3048,7 +3527,7 @@ From class boolean @@ -3061,10 +3540,10 @@ From class - View + View @@ -3080,7 +3559,7 @@ From class void @@ -3096,7 +3575,7 @@ From class boolean @@ -3109,7 +3588,7 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams @@ -3141,10 +3620,10 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams @@ -3157,7 +3636,7 @@ From class - View + View @@ -3240,7 +3719,7 @@ From class boolean @@ -3269,7 +3748,7 @@ From class - View + View @@ -3400,7 +3879,7 @@ From class void @@ -3413,10 +3892,10 @@ From class - ViewParent + ViewParent @@ -3544,7 +4023,7 @@ From class void @@ -3560,7 +4039,7 @@ From class void @@ -3592,7 +4071,7 @@ From class void @@ -3608,7 +4087,7 @@ From class void @@ -3672,7 +4151,7 @@ From class boolean @@ -3688,7 +4167,7 @@ From class boolean @@ -3720,7 +4199,7 @@ From class boolean @@ -3736,7 +4215,7 @@ From class boolean @@ -3752,7 +4231,7 @@ From class void @@ -3800,7 +4279,7 @@ From class void @@ -3816,7 +4295,7 @@ From class void @@ -3848,7 +4327,7 @@ From class void @@ -3896,7 +4375,7 @@ From class void @@ -3912,7 +4391,7 @@ From class boolean @@ -3944,7 +4423,7 @@ From class boolean @@ -3960,7 +4439,7 @@ From class boolean @@ -3976,7 +4455,7 @@ From class void @@ -4152,7 +4631,7 @@ From class void @@ -4168,7 +4647,7 @@ From class void @@ -4216,7 +4695,7 @@ From class void @@ -4296,7 +4775,7 @@ From class boolean @@ -4312,7 +4791,7 @@ From class ActionMode @@ -4344,7 +4823,7 @@ From class void @@ -4360,7 +4839,7 @@ From class void @@ -4380,7 +4859,7 @@ From class class="jd-expando-trigger-img" /> From class - android.view.View + android.view.View
        @@ -4418,7 +4897,7 @@ From class void @@ -4434,7 +4913,7 @@ From class void @@ -4482,7 +4961,7 @@ From class void @@ -4514,7 +4993,7 @@ From class void @@ -4706,7 +5185,7 @@ From class boolean @@ -4898,7 +5377,7 @@ From class void @@ -4930,7 +5409,7 @@ From class void @@ -4978,7 +5457,7 @@ From class void @@ -4994,7 +5473,7 @@ From class boolean @@ -5010,7 +5489,7 @@ From class boolean @@ -5026,7 +5505,7 @@ From class boolean @@ -5042,7 +5521,7 @@ From class boolean @@ -5058,7 +5537,7 @@ From class boolean @@ -5074,7 +5553,7 @@ From class boolean @@ -5090,7 +5569,7 @@ From class boolean @@ -5106,7 +5585,7 @@ From class boolean @@ -5122,7 +5601,7 @@ From class void @@ -5138,7 +5617,7 @@ From class void @@ -5218,7 +5697,7 @@ From class boolean @@ -5234,7 +5713,7 @@ From class boolean @@ -5250,7 +5729,7 @@ From class boolean @@ -5266,7 +5745,7 @@ From class void @@ -5330,7 +5809,7 @@ From class void @@ -5359,7 +5838,7 @@ From class - View + View @@ -5410,7 +5889,7 @@ From class void @@ -5426,7 +5905,7 @@ From class boolean @@ -5439,7 +5918,7 @@ From class - View + View @@ -5839,7 +6318,7 @@ From class - ArrayList<View> + ArrayList<View> @@ -5874,7 +6353,7 @@ From class boolean @@ -5890,7 +6369,7 @@ From class boolean @@ -5903,7 +6382,7 @@ From class - Handler + Handler @@ -6031,7 +6510,7 @@ From class - KeyEvent.DispatcherState + KeyEvent.DispatcherState @@ -6175,7 +6654,7 @@ From class - Matrix + Matrix @@ -7311,10 +7790,10 @@ From class static - View + View @@ -7330,7 +7809,7 @@ From class void @@ -7346,7 +7825,7 @@ From class void @@ -7362,7 +7841,7 @@ From class void @@ -7410,7 +7889,7 @@ From class void @@ -8066,7 +8545,7 @@ From class void @@ -8082,7 +8561,7 @@ From class void @@ -8111,10 +8590,10 @@ From class - InputConnection + InputConnection @@ -8178,7 +8657,7 @@ From class void @@ -8194,7 +8673,7 @@ From class void @@ -8210,7 +8689,7 @@ From class boolean @@ -8258,7 +8737,7 @@ From class void @@ -8274,7 +8753,7 @@ From class boolean @@ -8306,7 +8785,7 @@ From class boolean @@ -8322,7 +8801,7 @@ From class void @@ -8354,7 +8833,7 @@ From class boolean @@ -8370,7 +8849,7 @@ From class boolean @@ -8386,7 +8865,7 @@ From class boolean @@ -8402,7 +8881,7 @@ From class boolean @@ -8418,7 +8897,7 @@ From class boolean @@ -8434,7 +8913,7 @@ From class boolean @@ -8498,7 +8977,7 @@ From class void @@ -8514,7 +8993,7 @@ From class void @@ -8527,7 +9006,7 @@ From class - Parcelable + Parcelable @@ -8642,7 +9121,7 @@ From class boolean @@ -8658,7 +9137,7 @@ From class void @@ -8738,7 +9217,7 @@ From class boolean @@ -8834,7 +9313,7 @@ From class boolean @@ -8850,7 +9329,7 @@ From class boolean @@ -8962,7 +9441,7 @@ From class void @@ -8978,7 +9457,7 @@ From class void @@ -9010,7 +9489,7 @@ From class boolean @@ -9074,7 +9553,7 @@ From class boolean @@ -9154,7 +9633,7 @@ From class boolean @@ -9170,7 +9649,7 @@ From class boolean @@ -9218,7 +9697,7 @@ From class void @@ -9234,7 +9713,7 @@ From class void @@ -9250,7 +9729,7 @@ From class void @@ -9314,7 +9793,7 @@ From class void @@ -9378,7 +9857,7 @@ From class void @@ -9394,7 +9873,7 @@ From class void @@ -9426,7 +9905,7 @@ From class void @@ -9506,7 +9985,7 @@ From class void @@ -9810,7 +10289,7 @@ From class void @@ -9826,7 +10305,7 @@ From class void @@ -10002,7 +10481,7 @@ From class void @@ -10018,7 +10497,7 @@ From class void @@ -10050,7 +10529,7 @@ From class void @@ -10098,7 +10577,7 @@ From class void @@ -10114,7 +10593,7 @@ From class void @@ -10146,7 +10625,7 @@ From class void @@ -10546,7 +11025,7 @@ From class void @@ -10562,7 +11041,7 @@ From class void @@ -10594,7 +11073,7 @@ From class void @@ -10802,7 +11281,7 @@ From class void @@ -10818,7 +11297,7 @@ From class boolean @@ -10834,7 +11313,7 @@ From class void @@ -10850,7 +11329,7 @@ From class void @@ -10866,7 +11345,7 @@ From class boolean @@ -10918,7 +11397,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        @@ -10985,7 +11464,7 @@ From class final - Class<?> + Class<?> @@ -11158,7 +11637,7 @@ From interface void @@ -11174,7 +11653,7 @@ From interface void @@ -11194,7 +11673,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.KeyEvent.Callback + android.view.KeyEvent.Callback
        @@ -11232,7 +11711,7 @@ From interface boolean @@ -11248,7 +11727,7 @@ From interface boolean @@ -11264,7 +11743,7 @@ From interface boolean @@ -11284,7 +11763,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.ViewManager + android.view.ViewManager
        @@ -11322,7 +11801,7 @@ From interface void @@ -11338,7 +11817,7 @@ From interface void @@ -11358,7 +11837,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.ViewParent + android.view.ViewParent
        @@ -11396,7 +11875,7 @@ From interface void @@ -11412,7 +11891,7 @@ From interface void @@ -11428,7 +11907,7 @@ From interface void @@ -11441,10 +11920,10 @@ From interface - View + View @@ -11460,7 +11939,7 @@ From interface void @@ -11476,7 +11955,7 @@ From interface boolean @@ -11489,7 +11968,7 @@ From interface - ViewParent + ViewParent @@ -11537,10 +12016,10 @@ From interface - ViewParent + ViewParent @@ -11572,7 +12051,7 @@ From interface void @@ -11588,7 +12067,7 @@ From interface void @@ -11604,7 +12083,7 @@ From interface boolean @@ -11668,7 +12147,7 @@ From interface boolean @@ -11684,7 +12163,7 @@ From interface void @@ -11700,7 +12179,7 @@ From interface boolean @@ -11716,7 +12195,7 @@ From interface ActionMode @@ -11736,7 +12215,7 @@ From interface class="jd-expando-trigger-img" /> From interface - android.view.accessibility.AccessibilityEventSource + android.view.accessibility.AccessibilityEventSource
        @@ -11932,6 +12411,45 @@ From interface + + +
        +

        + + public + static + final + int + + DEFAULT_ACTIVITY_REQUEST_CODE +

        +
        + + + + +
        +
        + +

        An empty ActivityRequestCode to serve as the default before the code has been assigned. +

        + + +
        + Constant Value: + + + -1 + (0xffffffff) + + +
        + +
        +
        + + +
        @@ -12121,7 +12639,7 @@ From interface PlusOneButton - (Context context) + (Context context)
        @@ -12161,7 +12679,7 @@ From interface PlusOneButton - (Context context, AttributeSet attrs) + (Context context, AttributeSet attrs)
        @@ -12207,7 +12725,7 @@ From interface - +

        @@ -12220,7 +12738,7 @@ From interface void initialize - (PlusClient plusClient, String url, PlusOneButton.OnPlusOneClickListener plusOneClickListener) + (String url, PlusOneButton.OnPlusOneClickListener plusOneClickListener)

        @@ -12235,10 +12753,6 @@ From interface
        Parameters
        - [Expand] -
        Inherited Methods
        - addChildrenForAccessibility(ArrayList<View> arg0) + addChildrenForAccessibility(ArrayList<View> arg0)
        - addFocusables(ArrayList<View> arg0, int arg1, int arg2) + addFocusables(ArrayList<View> arg0, int arg1, int arg2)
        - addTouchables(ArrayList<View> arg0) + addTouchables(ArrayList<View> arg0)
        - addView(View arg0, int arg1, ViewGroup.LayoutParams arg2) + addView(View arg0, int arg1, ViewGroup.LayoutParams arg2)
        - addView(View arg0, ViewGroup.LayoutParams arg1) + addView(View arg0, ViewGroup.LayoutParams arg1)
        - addView(View arg0, int arg1) + addView(View arg0, int arg1)
        - addView(View arg0) + addView(View arg0)
        - addView(View arg0, int arg1, int arg2) + addView(View arg0, int arg1, int arg2)
        - addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2, boolean arg3) + addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2, boolean arg3)
        - addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2) + addViewInLayout(View arg0, int arg1, ViewGroup.LayoutParams arg2)
        - attachLayoutAnimationParameters(View arg0, ViewGroup.LayoutParams arg1, int arg2, int arg3) + attachLayoutAnimationParameters(View arg0, ViewGroup.LayoutParams arg1, int arg2, int arg3)
        - attachViewToParent(View arg0, int arg1, ViewGroup.LayoutParams arg2) + attachViewToParent(View arg0, int arg1, ViewGroup.LayoutParams arg2)
        - bringChildToFront(View arg0) + bringChildToFront(View arg0)
        - checkLayoutParams(ViewGroup.LayoutParams arg0) + checkLayoutParams(ViewGroup.LayoutParams arg0)
        - childDrawableStateChanged(View arg0) + childDrawableStateChanged(View arg0)
        - cleanupLayoutState(View arg0) + cleanupLayoutState(View arg0)
        - clearChildFocus(View arg0) + clearChildFocus(View arg0)
        - detachViewFromParent(View arg0) + detachViewFromParent(View arg0)
        - dispatchConfigurationChanged(Configuration arg0) + dispatchConfigurationChanged(Configuration arg0)
        - dispatchDraw(Canvas arg0) + dispatchDraw(Canvas arg0)
        - dispatchFreezeSelfOnly(SparseArray<Parcelable> arg0) + dispatchFreezeSelfOnly(SparseArray<Parcelable> arg0)
        - dispatchGenericFocusedEvent(MotionEvent arg0) + dispatchGenericFocusedEvent(MotionEvent arg0)
        - dispatchGenericPointerEvent(MotionEvent arg0) + dispatchGenericPointerEvent(MotionEvent arg0)
        - dispatchHoverEvent(MotionEvent arg0) + dispatchHoverEvent(MotionEvent arg0)
        - dispatchKeyEvent(KeyEvent arg0) + dispatchKeyEvent(KeyEvent arg0)
        - dispatchKeyEventPreIme(KeyEvent arg0) + dispatchKeyEventPreIme(KeyEvent arg0)
        - dispatchKeyShortcutEvent(KeyEvent arg0) + dispatchKeyShortcutEvent(KeyEvent arg0)
        - dispatchRestoreInstanceState(SparseArray<Parcelable> arg0) + dispatchRestoreInstanceState(SparseArray<Parcelable> arg0)
        - dispatchSaveInstanceState(SparseArray<Parcelable> arg0) + dispatchSaveInstanceState(SparseArray<Parcelable> arg0)
        - dispatchThawSelfOnly(SparseArray<Parcelable> arg0) + dispatchThawSelfOnly(SparseArray<Parcelable> arg0)
        - dispatchTouchEvent(MotionEvent arg0) + dispatchTouchEvent(MotionEvent arg0)
        - dispatchTrackballEvent(MotionEvent arg0) + dispatchTrackballEvent(MotionEvent arg0)
        - dispatchUnhandledMove(View arg0, int arg1) + dispatchUnhandledMove(View arg0, int arg1)
        - dispatchVisibilityChanged(View arg0, int arg1) + dispatchVisibilityChanged(View arg0, int arg1)
        - drawChild(Canvas arg0, View arg1, long arg2) + drawChild(Canvas arg0, View arg1, long arg2)
        - endViewTransition(View arg0) + endViewTransition(View arg0)
        findFocus() @@ -3032,7 +3511,7 @@ From class void - findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2) + findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2)
        - fitSystemWindows(Rect arg0) + fitSystemWindows(Rect arg0)
        - focusSearch(View arg0, int arg1) + focusSearch(View arg0, int arg1)
        - focusableViewAvailable(View arg0) + focusableViewAvailable(View arg0)
        - gatherTransparentRegion(Region arg0) + gatherTransparentRegion(Region arg0)
        generateDefaultLayoutParams() @@ -3125,10 +3604,10 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams - generateLayoutParams(AttributeSet arg0) + generateLayoutParams(AttributeSet arg0)
        - generateLayoutParams(ViewGroup.LayoutParams arg0) + generateLayoutParams(ViewGroup.LayoutParams arg0)
        getChildAt(int arg0) @@ -3224,7 +3703,7 @@ From class boolean - getChildStaticTransformation(View arg0, Transformation arg1) + getChildStaticTransformation(View arg0, Transformation arg1)
        - getChildVisibleRect(View arg0, Rect arg1, Point arg2) + getChildVisibleRect(View arg0, Rect arg1, Point arg2)
        getFocusedChild() @@ -3285,7 +3764,7 @@ From class - LayoutAnimationController + LayoutAnimationController getLayoutAnimation() @@ -3301,7 +3780,7 @@ From class - Animation.AnimationListener + Animation.AnimationListener getLayoutAnimationListener() @@ -3384,7 +3863,7 @@ From class int - indexOfChild(View arg0) + indexOfChild(View arg0)
        - invalidateChild(View arg0, Rect arg1) + invalidateChild(View arg0, Rect arg1)
        - invalidateChildInParent(int[] arg0, Rect arg1) + invalidateChildInParent(int[] arg0, Rect arg1)
        - measureChild(View arg0, int arg1, int arg2) + measureChild(View arg0, int arg1, int arg2)
        - measureChildWithMargins(View arg0, int arg1, int arg2, int arg3, int arg4) + measureChildWithMargins(View arg0, int arg1, int arg2, int arg3, int arg4)
        - offsetDescendantRectToMyCoords(View arg0, Rect arg1) + offsetDescendantRectToMyCoords(View arg0, Rect arg1)
        - offsetRectIntoDescendantCoords(View arg0, Rect arg1) + offsetRectIntoDescendantCoords(View arg0, Rect arg1)
        - onInterceptHoverEvent(MotionEvent arg0) + onInterceptHoverEvent(MotionEvent arg0)
        - onInterceptTouchEvent(MotionEvent arg0) + onInterceptTouchEvent(MotionEvent arg0)
        - onRequestFocusInDescendants(int arg0, Rect arg1) + onRequestFocusInDescendants(int arg0, Rect arg1)
        - onRequestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1) + onRequestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1)
        - recomputeViewAttributes(View arg0) + recomputeViewAttributes(View arg0)
        - removeDetachedView(View arg0, boolean arg1) + removeDetachedView(View arg0, boolean arg1)
        - removeView(View arg0) + removeView(View arg0)
        - removeViewInLayout(View arg0) + removeViewInLayout(View arg0)
        - requestChildFocus(View arg0, View arg1) + requestChildFocus(View arg0, View arg1)
        - requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2) + requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2)
        - requestFocus(int arg0, Rect arg1) + requestFocus(int arg0, Rect arg1)
        - requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1) + requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1)
        - requestTransparentRegion(View arg0) + requestTransparentRegion(View arg0)
        - setLayoutAnimation(LayoutAnimationController arg0) + setLayoutAnimation(LayoutAnimationController arg0)
        - setLayoutAnimationListener(Animation.AnimationListener arg0) + setLayoutAnimationListener(Animation.AnimationListener arg0)
        - setOnHierarchyChangeListener(ViewGroup.OnHierarchyChangeListener arg0) + setOnHierarchyChangeListener(ViewGroup.OnHierarchyChangeListener arg0)
        - showContextMenuForChild(View arg0) + showContextMenuForChild(View arg0)
        - startActionModeForChild(View arg0, ActionMode.Callback arg1) + startActionModeForChild(View arg0, ActionMode.Callback arg1)
        - startViewTransition(View arg0) + startViewTransition(View arg0)
        - updateViewLayout(View arg0, ViewGroup.LayoutParams arg1) + updateViewLayout(View arg0, ViewGroup.LayoutParams arg1)
        - addChildrenForAccessibility(ArrayList<View> arg0) + addChildrenForAccessibility(ArrayList<View> arg0)
        - addFocusables(ArrayList<View> arg0, int arg1, int arg2) + addFocusables(ArrayList<View> arg0, int arg1, int arg2)
        - addFocusables(ArrayList<View> arg0, int arg1) + addFocusables(ArrayList<View> arg0, int arg1)
        - addTouchables(ArrayList<View> arg0) + addTouchables(ArrayList<View> arg0)
        - announceForAccessibility(CharSequence arg0) + announceForAccessibility(CharSequence arg0)
        - checkInputConnectionProxy(View arg0) + checkInputConnectionProxy(View arg0)
        - createContextMenu(ContextMenu arg0) + createContextMenu(ContextMenu arg0)
        - dispatchConfigurationChanged(Configuration arg0) + dispatchConfigurationChanged(Configuration arg0)
        - dispatchDraw(Canvas arg0) + dispatchDraw(Canvas arg0)
        - dispatchGenericFocusedEvent(MotionEvent arg0) + dispatchGenericFocusedEvent(MotionEvent arg0)
        - dispatchGenericMotionEvent(MotionEvent arg0) + dispatchGenericMotionEvent(MotionEvent arg0)
        - dispatchGenericPointerEvent(MotionEvent arg0) + dispatchGenericPointerEvent(MotionEvent arg0)
        - dispatchHoverEvent(MotionEvent arg0) + dispatchHoverEvent(MotionEvent arg0)
        - dispatchKeyEvent(KeyEvent arg0) + dispatchKeyEvent(KeyEvent arg0)
        - dispatchKeyEventPreIme(KeyEvent arg0) + dispatchKeyEventPreIme(KeyEvent arg0)
        - dispatchKeyShortcutEvent(KeyEvent arg0) + dispatchKeyShortcutEvent(KeyEvent arg0)
        - dispatchPopulateAccessibilityEvent(AccessibilityEvent arg0) + dispatchPopulateAccessibilityEvent(AccessibilityEvent arg0)
        - dispatchRestoreInstanceState(SparseArray<Parcelable> arg0) + dispatchRestoreInstanceState(SparseArray<Parcelable> arg0)
        - dispatchSaveInstanceState(SparseArray<Parcelable> arg0) + dispatchSaveInstanceState(SparseArray<Parcelable> arg0)
        - dispatchTouchEvent(MotionEvent arg0) + dispatchTouchEvent(MotionEvent arg0)
        - dispatchTrackballEvent(MotionEvent arg0) + dispatchTrackballEvent(MotionEvent arg0)
        - dispatchUnhandledMove(View arg0, int arg1) + dispatchUnhandledMove(View arg0, int arg1)
        - dispatchVisibilityChanged(View arg0, int arg1) + dispatchVisibilityChanged(View arg0, int arg1)
        - draw(Canvas arg0) + draw(Canvas arg0)
        findFocus() @@ -5375,7 +5854,7 @@ From class final - View + View findViewById(int arg0) @@ -5391,10 +5870,10 @@ From class final - View + View - findViewWithTag(Object arg0) + findViewWithTag(Object arg0)
        - findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2) + findViewsWithText(ArrayList<View> arg0, CharSequence arg1, int arg2)
        - fitSystemWindows(Rect arg0) + fitSystemWindows(Rect arg0)
        focusSearch(int arg0) @@ -5503,7 +5982,7 @@ From class - Animation + Animation getAnimation() @@ -5519,7 +5998,7 @@ From class - IBinder + IBinder getApplicationWindowToken() @@ -5535,7 +6014,7 @@ From class - Drawable + Drawable getBackground() @@ -5631,7 +6110,7 @@ From class - CharSequence + CharSequence getContentDescription() @@ -5647,7 +6126,7 @@ From class final - Context + Context getContext() @@ -5663,7 +6142,7 @@ From class - ContextMenu.ContextMenuInfo + ContextMenu.ContextMenuInfo getContextMenuInfo() @@ -5711,7 +6190,7 @@ From class - Bitmap + Bitmap getDrawingCache(boolean arg0) @@ -5727,7 +6206,7 @@ From class - Bitmap + Bitmap getDrawingCache() @@ -5778,7 +6257,7 @@ From class void - getDrawingRect(Rect arg0) + getDrawingRect(Rect arg0)
        getFocusables(int arg0) @@ -5858,7 +6337,7 @@ From class void - getFocusedRect(Rect arg0) + getFocusedRect(Rect arg0)
        - getGlobalVisibleRect(Rect arg0, Point arg1) + getGlobalVisibleRect(Rect arg0, Point arg1)
        - getGlobalVisibleRect(Rect arg0) + getGlobalVisibleRect(Rect arg0)
        getHandler() @@ -5938,7 +6417,7 @@ From class void - getHitRect(Rect arg0) + getHitRect(Rect arg0)
        getKeyDispatcherState() @@ -6063,7 +6542,7 @@ From class - ViewGroup.LayoutParams + ViewGroup.LayoutParams getLayoutParams() @@ -6130,7 +6609,7 @@ From class boolean - getLocalVisibleRect(Rect arg0) + getLocalVisibleRect(Rect arg0)
        getMatrix() @@ -6383,7 +6862,7 @@ From class - View.OnFocusChangeListener + View.OnFocusChangeListener getOnFocusChangeListener() @@ -6479,7 +6958,7 @@ From class final - ViewParent + ViewParent getParent() @@ -6495,7 +6974,7 @@ From class - ViewParent + ViewParent getParentForAccessibility() @@ -6543,7 +7022,7 @@ From class - Resources + Resources getResources() @@ -6607,7 +7086,7 @@ From class - View + View getRootView() @@ -6863,7 +7342,7 @@ From class - Object + Object getTag(int arg0) @@ -6879,7 +7358,7 @@ From class - Object + Object getTag() @@ -6943,7 +7422,7 @@ From class - TouchDelegate + TouchDelegate getTouchDelegate() @@ -6959,7 +7438,7 @@ From class - ArrayList<View> + ArrayList<View> getTouchables() @@ -7055,7 +7534,7 @@ From class - ViewTreeObserver + ViewTreeObserver getViewTreeObserver() @@ -7135,7 +7614,7 @@ From class - IBinder + IBinder getWindowToken() @@ -7170,7 +7649,7 @@ From class void - getWindowVisibleDisplayFrame(Rect arg0) + getWindowVisibleDisplayFrame(Rect arg0)
        - inflate(Context arg0, int arg1, ViewGroup arg2) + inflate(Context arg0, int arg1, ViewGroup arg2)
        - initializeFadingEdge(TypedArray arg0) + initializeFadingEdge(TypedArray arg0)
        - initializeScrollbars(TypedArray arg0) + initializeScrollbars(TypedArray arg0)
        - invalidate(Rect arg0) + invalidate(Rect arg0)
        - invalidateDrawable(Drawable arg0) + invalidateDrawable(Drawable arg0)
        - onConfigurationChanged(Configuration arg0) + onConfigurationChanged(Configuration arg0)
        - onCreateContextMenu(ContextMenu arg0) + onCreateContextMenu(ContextMenu arg0)
        - onCreateInputConnection(EditorInfo arg0) + onCreateInputConnection(EditorInfo arg0)
        - onDraw(Canvas arg0) + onDraw(Canvas arg0)
        - onDrawScrollBars(Canvas arg0) + onDrawScrollBars(Canvas arg0)
        - onFilterTouchEventForSecurity(MotionEvent arg0) + onFilterTouchEventForSecurity(MotionEvent arg0)
        - onFocusChanged(boolean arg0, int arg1, Rect arg2) + onFocusChanged(boolean arg0, int arg1, Rect arg2)
        - onGenericMotionEvent(MotionEvent arg0) + onGenericMotionEvent(MotionEvent arg0)
        - onHoverEvent(MotionEvent arg0) + onHoverEvent(MotionEvent arg0)
        - onInitializeAccessibilityEvent(AccessibilityEvent arg0) + onInitializeAccessibilityEvent(AccessibilityEvent arg0)
        - onKeyDown(int arg0, KeyEvent arg1) + onKeyDown(int arg0, KeyEvent arg1)
        - onKeyLongPress(int arg0, KeyEvent arg1) + onKeyLongPress(int arg0, KeyEvent arg1)
        - onKeyMultiple(int arg0, int arg1, KeyEvent arg2) + onKeyMultiple(int arg0, int arg1, KeyEvent arg2)
        - onKeyPreIme(int arg0, KeyEvent arg1) + onKeyPreIme(int arg0, KeyEvent arg1)
        - onKeyShortcut(int arg0, KeyEvent arg1) + onKeyShortcut(int arg0, KeyEvent arg1)
        - onKeyUp(int arg0, KeyEvent arg1) + onKeyUp(int arg0, KeyEvent arg1)
        - onPopulateAccessibilityEvent(AccessibilityEvent arg0) + onPopulateAccessibilityEvent(AccessibilityEvent arg0)
        - onRestoreInstanceState(Parcelable arg0) + onRestoreInstanceState(Parcelable arg0)
        onSaveInstanceState() @@ -8626,7 +9105,7 @@ From class boolean - onTouchEvent(MotionEvent arg0) + onTouchEvent(MotionEvent arg0)
        - onTrackballEvent(MotionEvent arg0) + onTrackballEvent(MotionEvent arg0)
        - onVisibilityChanged(View arg0, int arg1) + onVisibilityChanged(View arg0, int arg1)
        - performAccessibilityAction(int arg0, Bundle arg1) + performAccessibilityAction(int arg0, Bundle arg1)
        - post(Runnable arg0) + post(Runnable arg0)
        - postDelayed(Runnable arg0, long arg1) + postDelayed(Runnable arg0, long arg1)
        - postOnAnimation(Runnable arg0) + postOnAnimation(Runnable arg0)
        - postOnAnimationDelayed(Runnable arg0, long arg1) + postOnAnimationDelayed(Runnable arg0, long arg1)
        - removeCallbacks(Runnable arg0) + removeCallbacks(Runnable arg0)
        - requestFocus(int arg0, Rect arg1) + requestFocus(int arg0, Rect arg1)
        - requestRectangleOnScreen(Rect arg0) + requestRectangleOnScreen(Rect arg0)
        - requestRectangleOnScreen(Rect arg0, boolean arg1) + requestRectangleOnScreen(Rect arg0, boolean arg1)
        - restoreHierarchyState(SparseArray<Parcelable> arg0) + restoreHierarchyState(SparseArray<Parcelable> arg0)
        - saveHierarchyState(SparseArray<Parcelable> arg0) + saveHierarchyState(SparseArray<Parcelable> arg0)
        - scheduleDrawable(Drawable arg0, Runnable arg1, long arg2) + scheduleDrawable(Drawable arg0, Runnable arg1, long arg2)
        - sendAccessibilityEventUnchecked(AccessibilityEvent arg0) + sendAccessibilityEventUnchecked(AccessibilityEvent arg0)
        - setAnimation(Animation arg0) + setAnimation(Animation arg0)
        - setBackground(Drawable arg0) + setBackground(Drawable arg0)
        - setBackgroundDrawable(Drawable arg0) + setBackgroundDrawable(Drawable arg0)
        - setContentDescription(CharSequence arg0) + setContentDescription(CharSequence arg0)
        - setLayerType(int arg0, Paint arg1) + setLayerType(int arg0, Paint arg1)
        - setLayoutParams(ViewGroup.LayoutParams arg0) + setLayoutParams(ViewGroup.LayoutParams arg0)
        - setOnClickListener(View.OnClickListener arg0) + setOnClickListener(View.OnClickListener arg0)
        - setOnCreateContextMenuListener(View.OnCreateContextMenuListener arg0) + setOnCreateContextMenuListener(View.OnCreateContextMenuListener arg0)
        - setOnFocusChangeListener(View.OnFocusChangeListener arg0) + setOnFocusChangeListener(View.OnFocusChangeListener arg0)
        - setOnKeyListener(View.OnKeyListener arg0) + setOnKeyListener(View.OnKeyListener arg0)
        - setOnLongClickListener(View.OnLongClickListener arg0) + setOnLongClickListener(View.OnLongClickListener arg0)
        - setOnTouchListener(View.OnTouchListener arg0) + setOnTouchListener(View.OnTouchListener arg0)
        - setTag(int arg0, Object arg1) + setTag(int arg0, Object arg1)
        - setTag(Object arg0) + setTag(Object arg0)
        - setTouchDelegate(TouchDelegate arg0) + setTouchDelegate(TouchDelegate arg0)
        - startAnimation(Animation arg0) + startAnimation(Animation arg0)
        - startDrag(ClipData arg0, View.DragShadowBuilder arg1, Object arg2, int arg3) + startDrag(ClipData arg0, View.DragShadowBuilder arg1, Object arg2, int arg3)
        - unscheduleDrawable(Drawable arg0) + unscheduleDrawable(Drawable arg0)
        - unscheduleDrawable(Drawable arg0, Runnable arg1) + unscheduleDrawable(Drawable arg0, Runnable arg1)
        - verifyDrawable(Drawable arg0) + verifyDrawable(Drawable arg0)
        clone() @@ -10956,7 +11435,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -11049,7 +11528,7 @@ From class - String + String toString() @@ -11120,7 +11599,7 @@ From class class="jd-expando-trigger-img" /> From interface - android.graphics.drawable.Drawable.Callback + android.graphics.drawable.Drawable.Callback
        - invalidateDrawable(Drawable arg0) + invalidateDrawable(Drawable arg0)
        - scheduleDrawable(Drawable arg0, Runnable arg1, long arg2) + scheduleDrawable(Drawable arg0, Runnable arg1, long arg2)
        - unscheduleDrawable(Drawable arg0, Runnable arg1) + unscheduleDrawable(Drawable arg0, Runnable arg1)
        - onKeyDown(int arg0, KeyEvent arg1) + onKeyDown(int arg0, KeyEvent arg1)
        - onKeyLongPress(int arg0, KeyEvent arg1) + onKeyLongPress(int arg0, KeyEvent arg1)
        - onKeyMultiple(int arg0, int arg1, KeyEvent arg2) + onKeyMultiple(int arg0, int arg1, KeyEvent arg2)
        - onKeyUp(int arg0, KeyEvent arg1) + onKeyUp(int arg0, KeyEvent arg1)
        - addView(View arg0, ViewGroup.LayoutParams arg1) + addView(View arg0, ViewGroup.LayoutParams arg1)
        - removeView(View arg0) + removeView(View arg0)
        - updateViewLayout(View arg0, ViewGroup.LayoutParams arg1) + updateViewLayout(View arg0, ViewGroup.LayoutParams arg1)
        - bringChildToFront(View arg0) + bringChildToFront(View arg0)
        - childDrawableStateChanged(View arg0) + childDrawableStateChanged(View arg0)
        - clearChildFocus(View arg0) + clearChildFocus(View arg0)
        - createContextMenu(ContextMenu arg0) + createContextMenu(ContextMenu arg0)
        - focusSearch(View arg0, int arg1) + focusSearch(View arg0, int arg1)
        - focusableViewAvailable(View arg0) + focusableViewAvailable(View arg0)
        - getChildVisibleRect(View arg0, Rect arg1, Point arg2) + getChildVisibleRect(View arg0, Rect arg1, Point arg2)
        getParent() @@ -11505,7 +11984,7 @@ From interface - ViewParent + ViewParent getParentForAccessibility() @@ -11524,7 +12003,7 @@ From interface void - invalidateChild(View arg0, Rect arg1) + invalidateChild(View arg0, Rect arg1)
        - invalidateChildInParent(int[] arg0, Rect arg1) + invalidateChildInParent(int[] arg0, Rect arg1)
        - recomputeViewAttributes(View arg0) + recomputeViewAttributes(View arg0)
        - requestChildFocus(View arg0, View arg1) + requestChildFocus(View arg0, View arg1)
        - requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2) + requestChildRectangleOnScreen(View arg0, Rect arg1, boolean arg2)
        - requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1) + requestSendAccessibilityEvent(View arg0, AccessibilityEvent arg1)
        - requestTransparentRegion(View arg0) + requestTransparentRegion(View arg0)
        - showContextMenuForChild(View arg0) + showContextMenuForChild(View arg0)
        - startActionModeForChild(View arg0, ActionMode.Callback arg1) + startActionModeForChild(View arg0, ActionMode.Callback arg1)
        - sendAccessibilityEventUnchecked(AccessibilityEvent arg0) + sendAccessibilityEventUnchecked(AccessibilityEvent arg0)
        - - - @@ -12255,7 +12769,7 @@ From interface - +

        @@ -12268,7 +12782,7 @@ From interface void initialize - (PlusClient plusClient, String url, int activityRequestCode) + (String url, int activityRequestCode)

        @@ -12281,14 +12795,10 @@ From interface

        Updates the +1 button with a PlusClient and URL. Most apps call this method each time the button is in focus (for example, in the Activity onResume method). To use this method, the PlusOneButton must be placed in an Activity. Use - initialize(PlusClient, String, OnPlusOneClickListener) otherwise.

        + initialize(String, OnPlusOneClickListener) otherwise.

        Parameters
        plusClient - The PlusClient.
        url The URL to be +1'd.
        - - - @@ -12435,6 +12945,69 @@ From interface +

        Protected Methods

        + + + + + +
        +

        + + protected + static + + + + int + + getAnnotation + (Context context, AttributeSet attrs) +

        +
        +
        + + + +
        +
        + +

        + +
        +
        + + + + +
        +

        + + protected + static + + + + int + + getSize + (Context context, AttributeSet attrs) +

        +
        +
        + + + +
        +
        + +

        + +
        +
        + + + diff --git a/docs/html/reference/com/google/android/gms/plus/PlusOneButtonWithPopup.html b/docs/html/reference/com/google/android/gms/plus/PlusOneButtonWithPopup.html new file mode 100644 index 0000000000000000000000000000000000000000..fb77613efb4a2007195b738cacb087282c2fb7e3 --- /dev/null +++ b/docs/html/reference/com/google/android/gms/plus/PlusOneButtonWithPopup.html @@ -0,0 +1,12263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PlusOneButtonWithPopup | Android Developers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + + + + +
        +
        + + + + +
        + public + + final + + class +

        PlusOneButtonWithPopup

        + + + + + + + + + + + + + extends ViewGroup
        + + + + + + + + + +
        + +
        + +
        +
        plusClient - The PlusClient.
        url The URL to be +1'd.
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        java.lang.Object
           ↳android.view.View
            ↳android.view.ViewGroup
             ↳com.google.android.gms.plus.PlusOneButtonWithPopup
        + + + + + + + +
        + + +

        Class Overview

        +

        +1 button which shows confirmation messages in a PopupWindow. +

        + + + + + +
        + + + + + + + + + + + + + + + + +
        + + +

        Summary

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + [Expand] +
        Inherited Constants
        + + From class +android.view.ViewGroup +
        + + +
        +
        + + From class +android.view.View +
        + + +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + [Expand] +
        Inherited Fields
        + + From class +android.view.View +
        + + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        Public Constructors
        + + + + + + + + PlusOneButtonWithPopup(Context context) + +
        Constructor to create from code.
        + +
        + + + + + + + + PlusOneButtonWithPopup(Context context, AttributeSet attrs) + +
        Constructor called when inflating from XML.
        + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        Public Methods
        + + + + + + void + + cancelClick() + +
        Restore the original +1 button state.
        + +
        + + + + + + PendingIntent + + getResolution() + +
        Returns a pending intent to resolve the connection failure or null if there + was none.
        + +
        + + + + + + void + + initialize(String url, String accountName) + +
        Updates the +1 button for the argument URL and account.
        + +
        + + + + + + void + + reinitialize() + +
        Reload the +1 button state.
        + +
        + + + + + + void + + setAnnotation(int annotation) + +
        Sets the annotation to display next to the button.
        + +
        + + + + + + void + + setOnClickListener(View.OnClickListener onClickListener) + +
        Sets the View.OnClickListener to handle clicks.
        + +
        + + + + + + void + + setSize(int size) + +
        Sets the size of the +1 button image.
        + +
        + + + + + + + + + + + + + + + + + + + + + + +
        Protected Methods
        + + + + + + void + + onLayout(boolean changed, int left, int top, int right, int bottom) + +
        + + + + + + void + + onMeasure(int widthMeasureSpec, int heightMeasureSpec) + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + [Expand] +
        Inherited Methods
        + +From class + + android.view.ViewGroup + +
        + + +
        +
        + +From class + + android.view.View + +
        + + +
        +
        + +From class + + java.lang.Object + +
        + + +
        +
        + +From interface + + android.graphics.drawable.Drawable.Callback + +
        + + +
        +
        + +From interface + + android.view.KeyEvent.Callback + +
        + + +
        +
        + +From interface + + android.view.ViewManager + +
        + + +
        +
        + +From interface + + android.view.ViewParent + +
        + + +
        +
        + +From interface + + android.view.accessibility.AccessibilityEventSource + +
        + + +
        +
        + + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + +

        Public Constructors

        + + + + + +
        +

        + + public + + + + + + + PlusOneButtonWithPopup + (Context context) +

        +
        +
        + + + +
        +
        + +

        Constructor to create from code. +

        + +
        +
        + + + + +
        +

        + + public + + + + + + + PlusOneButtonWithPopup + (Context context, AttributeSet attrs) +

        +
        +
        + + + +
        +
        + +

        Constructor called when inflating from XML. +

        + +
        +
        + + + + + + + + + + + + + +

        Public Methods

        + + + + + +
        +

        + + public + + + + + void + + cancelClick + () +

        +
        +
        + + + +
        +
        + +

        Restore the original +1 button state. +

        + +
        +
        + + + + +
        +

        + + public + + + + + PendingIntent + + getResolution + () +

        +
        +
        + + + +
        +
        + +

        Returns a pending intent to resolve the connection failure or null if there + was none. +

        + +
        +
        + + + + +
        +

        + + public + + + + + void + + initialize + (String url, String accountName) +

        +
        +
        + + + +
        +
        + +

        Updates the +1 button for the argument URL and account.

        +
        +
        Parameters
        + + + + + + + +
        url + The URL to be +1'd.
        accountName + The the name of the account tied to this +1 button (may be null). +
        +
        + +
        +
        + + + + +
        +

        + + public + + + + + void + + reinitialize + () +

        +
        +
        + + + +
        +
        + +

        Reload the +1 button state. +

        + +
        +
        + + + + +
        +

        + + public + + + + + void + + setAnnotation + (int annotation) +

        +
        +
        + + + +
        +
        + +

        Sets the annotation to display next to the button.

        +
        +
        Parameters
        + + + + +
        annotation + The annotation. See ANNOTATION_NONE, + ANNOTATION_INLINE, + and ANNOTATION_BUBBLE. +
        +
        + +
        +
        + + + + +
        +

        + + public + + + + + void + + setOnClickListener + (View.OnClickListener onClickListener) +

        +
        +
        + + + +
        +
        + +

        Sets the View.OnClickListener to handle clicks.

        +
        +
        Parameters
        + + + + +
        onClickListener + The listener, or null for default behavior. +
        +
        + +
        +
        + + + + +
        +

        + + public + + + + + void + + setSize + (int size) +

        +
        +
        + + + +
        +
        + +

        Sets the size of the +1 button image.

        +
        +
        Parameters
        + + + + +
        size + The size. See SIZE_STANDARD, + SIZE_TALL, SIZE_MEDIUM, and + SIZE_SMALL. +
        +
        + +
        +
        + + + + + + + +

        Protected Methods

        + + + + + +
        +

        + + protected + + + + + void + + onLayout + (boolean changed, int left, int top, int right, int bottom) +

        +
        +
        + + + +
        +
        + +

        + +
        +
        + + + + +
        +

        + + protected + + + + + void + + onMeasure + (int widthMeasureSpec, int heightMeasureSpec) +

        +
        +
        + + + +
        +
        + +

        + +
        +
        + + + + + + + + + +
        + +
        + + + + + + + + + + diff --git a/docs/html/reference/com/google/android/gms/plus/PlusOneDummyView.html b/docs/html/reference/com/google/android/gms/plus/PlusOneDummyView.html new file mode 100644 index 0000000000000000000000000000000000000000..e3fbd0d174a88e088cc8bea2c9c27a626d9d9fd5 --- /dev/null +++ b/docs/html/reference/com/google/android/gms/plus/PlusOneDummyView.html @@ -0,0 +1,12177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PlusOneDummyView | Android Developers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + + + + +
        +
        + + + + +
        + public + + + + class +

        PlusOneDummyView

        + + + + + + + + + + + + + + + + + extends FrameLayout
        + + + + + + + + + +
        + +
        + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        java.lang.Object
           ↳android.view.View
            ↳android.view.ViewGroup
             ↳android.widget.FrameLayout
              ↳com.google.android.gms.plus.PlusOneDummyView
        + + + + + + + +
        + + +

        Class Overview

        +

        A class used to statically generate dummy views in the event of an error retrieving + a PlusOneButton from the apk +

        + + + + + +
        + + + + + + + + + + + + + + + + +
        + + +

        Summary

        + + + + + + + + + + + + + + + + + + + + + + + + + + +
        Constants
        StringTAG
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + [Expand] +
        Inherited Constants
        + + From class +android.view.ViewGroup +
        + + +
        +
        + + From class +android.view.View +
        + + +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + [Expand] +
        Inherited Fields
        + + From class +android.view.View +
        + + +
        +
        + + + + + + + + + + + + + + + + +
        Public Constructors
        + + + + + + + + PlusOneDummyView(Context context, int size) + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + [Expand] +
        Inherited Methods
        + +From class + + android.widget.FrameLayout + +
        + + +
        +
        + +From class + + android.view.ViewGroup + +
        + + +
        +
        + +From class + + android.view.View + +
        + + +
        +
        + +From class + + java.lang.Object + +
        + + +
        +
        + +From interface + + android.graphics.drawable.Drawable.Callback + +
        + + +
        +
        + +From interface + + android.view.KeyEvent.Callback + +
        + + +
        +
        + +From interface + + android.view.ViewManager + +
        + + +
        +
        + +From interface + + android.view.ViewParent + +
        + + +
        +
        + +From interface + + android.view.accessibility.AccessibilityEventSource + +
        + + +
        +
        + + +
        + + + + + + + + + + + + + + + + + + + + +

        Constants

        + + + + + + +
        +

        + + public + static + final + String + + TAG +

        +
        + + + + +
        +
        + +

        + + +
        + Constant Value: + + + "PlusOneDummyView" + + +
        + +
        +
        + + + + + + + + + + + +

        Public Constructors

        + + + + + +
        +

        + + public + + + + + + + PlusOneDummyView + (Context context, int size) +

        +
        +
        + + + +
        +
        + +

        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + + +
        + +
        + +
        + + + + + + + + diff --git a/docs/html/reference/com/google/android/gms/plus/PlusShare.Builder.html b/docs/html/reference/com/google/android/gms/plus/PlusShare.Builder.html index a251cb939ed9a47d07bd2e31e119f6eb8b816cb1..80da92b6c615f26fbb14c4ca6dd71eb74025b161 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusShare.Builder.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusShare.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusShare.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - PlusShare.Builder(Context context) + PlusShare.Builder(Context context)
        Create a new Builder for launching a sharing action from the given context.
        @@ -809,7 +816,7 @@ Summary:
        - PlusShare.Builder(Activity launchingActivity) + PlusShare.Builder(Activity launchingActivity)
        Create a new Builder for launching a sharing action from launchingActivity.
        @@ -827,7 +834,7 @@ Summary:
        - PlusShare.Builder(Activity launchingActivity, PlusClient plusClient) + PlusShare.Builder(Activity launchingActivity, PlusClient plusClient)
        Create a new Builder for launching a sharing action from launchingActivity.
        @@ -858,7 +865,7 @@ Summary: PlusShare.Builder
        - addCallToAction(String label, Uri uri, String deepLinkId) + addCallToAction(String label, Uri uri, String deepLinkId)
        Adds a call-to-action button for an interactive post.
        @@ -876,7 +883,7 @@ Summary: PlusShare.Builder
        - addStream(Uri streamUri) + addStream(Uri streamUri)
        Add a stream URI to the data that should be shared.
        @@ -891,7 +898,7 @@ Summary: - Intent + Intent
        getIntent() @@ -912,7 +919,7 @@ Summary: PlusShare.Builder - setContentDeepLinkId(String deepLinkId, String title, String description, Uri thumbnailUri) + setContentDeepLinkId(String deepLinkId, String title, String description, Uri thumbnailUri)
        Include a deep-link ID to a resource to share on Google+.
        @@ -930,7 +937,7 @@ Summary: PlusShare.Builder
        - setContentDeepLinkId(String deepLinkId) + setContentDeepLinkId(String deepLinkId)
        Include a deep-link URI of a resource to share on Google+.
        @@ -948,7 +955,7 @@ Summary: PlusShare.Builder
        - setContentUrl(Uri uri) + setContentUrl(Uri uri)
        Sets a URL to link to from the content on the web.
        @@ -966,7 +973,7 @@ Summary: PlusShare.Builder
        - setRecipients(List<Person> recipientList) + setRecipients(List<Person> recipientList)
        Sets a list of people to send the interactive post to.
        @@ -984,7 +991,7 @@ Summary: PlusShare.Builder
        - setStream(Uri streamUri) + setStream(Uri streamUri)
        Set a stream URI to the data that should be shared.
        @@ -1002,7 +1009,7 @@ Summary: PlusShare.Builder
        - setText(CharSequence text) + setText(CharSequence text)
        Set a pre-filled message to be sent as part of the share.
        @@ -1020,7 +1027,7 @@ Summary: PlusShare.Builder
        - setType(String mimeType) + setType(String mimeType)
        Set the type of data being shared.
        @@ -1077,7 +1084,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1115,7 +1122,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1208,7 +1215,7 @@ From class - String + String toString() @@ -1317,7 +1324,7 @@ From class PlusShare.Builder - (Context context) + (Context context)
        @@ -1356,7 +1363,7 @@ From class PlusShare.Builder - (Activity launchingActivity) + (Activity launchingActivity)
        @@ -1395,7 +1402,7 @@ From class PlusShare.Builder - (Activity launchingActivity, PlusClient plusClient) + (Activity launchingActivity, PlusClient plusClient)
        @@ -1455,7 +1462,7 @@ From class PlusShare.Builder addCallToAction - (String label, Uri uri, String deepLinkId) + (String label, Uri uri, String deepLinkId)
        @@ -1509,7 +1516,7 @@ From class PlusShare.Builder addStream - (Uri streamUri) + (Uri streamUri)
        @@ -1555,7 +1562,7 @@ From class - Intent + Intent getIntent () @@ -1592,7 +1599,7 @@ From class PlusShare.Builder setContentDeepLinkId - (String deepLinkId, String title, String description, Uri thumbnailUri) + (String deepLinkId, String title, String description, Uri thumbnailUri)
        @@ -1651,7 +1658,7 @@ From class PlusShare.Builder setContentDeepLinkId - (String deepLinkId) + (String deepLinkId)
        @@ -1695,7 +1702,7 @@ From class PlusShare.Builder setContentUrl - (Uri uri) + (Uri uri)
        @@ -1740,7 +1747,7 @@ From class PlusShare.Builder setRecipients - (List<Person> recipientList) + (List<Person> recipientList)
        @@ -1759,7 +1766,7 @@ From class
        recipientList - A list of recipients. See loadPeople(PlusClient.OnPeopleLoadedListener, int) and + A list of recipients. See loadPeople(PlusClient.OnPeopleLoadedListener, String...) and createPerson(String, String).
        @@ -1787,7 +1794,7 @@ From class PlusShare.Builder setStream - (Uri streamUri) + (Uri streamUri)
        @@ -1837,7 +1844,7 @@ From class PlusShare.Builder setText - (CharSequence text) + (CharSequence text)
        @@ -1885,7 +1892,7 @@ From class PlusShare.Builder setType - (String mimeType) + (String mimeType)
        diff --git a/docs/html/reference/com/google/android/gms/plus/PlusShare.html b/docs/html/reference/com/google/android/gms/plus/PlusShare.html index 9eb917d549d328c123a53cdc0d4b4921f06c6fb6..983c4eeaf674e65c8b06bb54b010ff5e66117bc9 100644 --- a/docs/html/reference/com/google/android/gms/plus/PlusShare.html +++ b/docs/html/reference/com/google/android/gms/plus/PlusShare.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PlusShare | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        StringString EXTRA_CALL_TO_ACTION Used as a bundle extra field to describe a call-to-action button for a post on Google+.
        StringString EXTRA_CONTENT_DEEP_LINK_ID Used as a string extra field in ACTION_SEND intents to specify a resource to be shared on Google+.
        StringString EXTRA_CONTENT_DEEP_LINK_METADATA Used as a bundle extra field in ACTION_SEND intents to describe a resource to be shared on Google+.
        StringString EXTRA_CONTENT_URL This is a URL for the content of the post.
        StringString EXTRA_IS_INTERACTIVE_POST Extra indicating that this is an interactive post.
        StringString EXTRA_SENDER_ID The ID of the sender on Google+.
        StringString KEY_CALL_TO_ACTION_DEEP_LINK_ID Bundle key used for the String deep-link ID of the call-to-action button.
        StringString KEY_CALL_TO_ACTION_LABEL Bundle key used for the String label placeholder text of the call-to-action button.
        StringString KEY_CALL_TO_ACTION_URL Bundle key used for the String URL of the call-to-action button.
        StringString KEY_CONTENT_DEEP_LINK_METADATA_DESCRIPTION Bundle key used for the String description of the resource shared on Google+.
        StringString KEY_CONTENT_DEEP_LINK_METADATA_THUMBNAIL_URL Bundle key used for the String thumbnail URL of the resource shared on Google+.
        StringString KEY_CONTENT_DEEP_LINK_METADATA_TITLE Bundle key used for the String title of the resource shared on Google+.
        StringString PARAM_CONTENT_DEEP_LINK_ID The query parameter containing the deep-link ID.
        - createPerson(String id, String displayName) + createPerson(String id, String displayName)
        Creates a person to use as a recipient with the given ID and display name.
        @@ -969,10 +979,10 @@ Summary: static - String + String
        - getDeepLinkId(Intent intent) + getDeepLinkId(Intent intent)
        Get the incoming deep link.
        @@ -985,6 +995,33 @@ Summary: + + + + + + + + + + + + +
        Protected Methods
        + + + + static + + boolean + + isDeepLinkIdValid(String deepLinkId) + +
        Determine if the deep-link ID is valid.
        + +
        + + @@ -1002,7 +1039,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1040,7 +1077,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1133,7 +1170,7 @@ From class - String + String toString() @@ -1232,7 +1269,7 @@ From class public static final - String + String EXTRA_CALL_TO_ACTION @@ -1270,7 +1307,7 @@ From class public static final - String + String EXTRA_CONTENT_DEEP_LINK_ID @@ -1309,7 +1346,7 @@ From class public static final - String + String EXTRA_CONTENT_DEEP_LINK_METADATA @@ -1349,7 +1386,7 @@ From class public static final - String + String EXTRA_CONTENT_URL @@ -1387,7 +1424,7 @@ From class public static final - String + String EXTRA_IS_INTERACTIVE_POST @@ -1425,7 +1462,7 @@ From class public static final - String + String EXTRA_SENDER_ID @@ -1467,7 +1504,7 @@ From class public static final - String + String KEY_CALL_TO_ACTION_DEEP_LINK_ID @@ -1506,7 +1543,7 @@ From class public static final - String + String KEY_CALL_TO_ACTION_LABEL @@ -1545,7 +1582,7 @@ From class public static final - String + String KEY_CALL_TO_ACTION_URL @@ -1584,7 +1621,7 @@ From class public static final - String + String KEY_CONTENT_DEEP_LINK_METADATA_DESCRIPTION @@ -1623,7 +1660,7 @@ From class public static final - String + String KEY_CONTENT_DEEP_LINK_METADATA_THUMBNAIL_URL @@ -1662,7 +1699,7 @@ From class public static final - String + String KEY_CONTENT_DEEP_LINK_METADATA_TITLE @@ -1701,7 +1738,7 @@ From class public static final - String + String PARAM_CONTENT_DEEP_LINK_ID @@ -1803,7 +1840,7 @@ From class Person createPerson - (String id, String displayName) + (String id, String displayName)
        @@ -1844,10 +1881,10 @@ From class - String + String getDeepLinkId - (Intent intent) + (Intent intent)
        @@ -1882,6 +1919,45 @@ From class +

        Protected Methods

        + + + + + +
        +

        + + protected + static + + + + boolean + + isDeepLinkIdValid + (String deepLinkId) +

        +
        +
        + + + +
        +
        + +

        Determine if the deep-link ID is valid.

        +
        +
        Returns
        +
        • True if the provided deep-link ID is valid. +
        +
        + +
        +
        + + + diff --git a/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.Builder.html b/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.Builder.html index 2a8d1a2718b41de8bea5685b4ea779027a26c304..bafdfe9722ca40a5ee8931a6f9e8093a5b9ef277 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.Builder.html +++ b/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + ItemScope.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - setAdditionalName(List<String> additionalName) + setAdditionalName(List<String> additionalName)
        An additional name for a Person, can be used for a middle name.
        @@ -891,7 +898,7 @@ Summary: ItemScope.Builder
        - setAddressCountry(String addressCountry) + setAddressCountry(String addressCountry)
        Address country.
        @@ -909,7 +916,7 @@ Summary: ItemScope.Builder
        - setAddressLocality(String addressLocality) + setAddressLocality(String addressLocality)
        Address locality.
        @@ -927,7 +934,7 @@ Summary: ItemScope.Builder
        - setAddressRegion(String addressRegion) + setAddressRegion(String addressRegion)
        Address region.
        @@ -945,7 +952,7 @@ Summary: ItemScope.Builder
        - setAssociated_media(List<ItemScope> associated_media) + setAssociated_media(List<ItemScope> associated_media)
        The encoding.
        @@ -981,7 +988,7 @@ Summary: ItemScope.Builder
        - setAttendees(List<ItemScope> attendees) + setAttendees(List<ItemScope> attendees)
        A person attending the event.
        @@ -1017,7 +1024,7 @@ Summary: ItemScope.Builder
        - setAuthor(List<ItemScope> author) + setAuthor(List<ItemScope> author)
        The person or persons who created this result.
        @@ -1035,7 +1042,7 @@ Summary: ItemScope.Builder
        - setBestRating(String bestRating) + setBestRating(String bestRating)
        Best possible rating value that a result might obtain.
        @@ -1053,7 +1060,7 @@ Summary: ItemScope.Builder
        - setBirthDate(String birthDate) + setBirthDate(String birthDate)
        Date of birth.
        @@ -1089,7 +1096,7 @@ Summary: ItemScope.Builder
        - setCaption(String caption) + setCaption(String caption)
        The caption for this object.
        @@ -1107,7 +1114,7 @@ Summary: ItemScope.Builder
        - setContentSize(String contentSize) + setContentSize(String contentSize)
        File size in (mega/kilo) bytes.
        @@ -1125,7 +1132,7 @@ Summary: ItemScope.Builder
        - setContentUrl(String contentUrl) + setContentUrl(String contentUrl)
        Actual bytes of the media object, for example the image file or video file.
        @@ -1143,7 +1150,7 @@ Summary: ItemScope.Builder
        - setContributor(List<ItemScope> contributor) + setContributor(List<ItemScope> contributor)
        A list of contributors to this result.
        @@ -1161,7 +1168,7 @@ Summary: ItemScope.Builder
        - setDateCreated(String dateCreated) + setDateCreated(String dateCreated)
        The date the result was created such as the date that a review was first created.
        @@ -1179,7 +1186,7 @@ Summary: ItemScope.Builder
        - setDateModified(String dateModified) + setDateModified(String dateModified)
        The date the result was last modified such as the date that a review was last edited.
        @@ -1197,7 +1204,7 @@ Summary: ItemScope.Builder
        - setDatePublished(String datePublished) + setDatePublished(String datePublished)
        The initial date that the result was published.
        @@ -1215,7 +1222,7 @@ Summary: ItemScope.Builder
        - setDescription(String description) + setDescription(String description)
        The string that describes the content of the result.
        @@ -1233,7 +1240,7 @@ Summary: ItemScope.Builder
        - setDuration(String duration) + setDuration(String duration)
        The duration of the item (movie, audio recording, event, etc.) in ISO 8601 date format.
        @@ -1251,7 +1258,7 @@ Summary: ItemScope.Builder
        - setEmbedUrl(String embedUrl) + setEmbedUrl(String embedUrl)
        A URL pointing to a player for a specific video.
        @@ -1269,7 +1276,7 @@ Summary: ItemScope.Builder
        - setEndDate(String endDate) + setEndDate(String endDate)
        The end date and time of the event (in ISO 8601 date format).
        @@ -1287,7 +1294,7 @@ Summary: ItemScope.Builder
        - setFamilyName(String familyName) + setFamilyName(String familyName)
        Family name.
        @@ -1305,7 +1312,7 @@ Summary: ItemScope.Builder
        - setGender(String gender) + setGender(String gender)
        Gender of the person.
        @@ -1341,7 +1348,7 @@ Summary: ItemScope.Builder
        - setGivenName(String givenName) + setGivenName(String givenName)
        Given name.
        @@ -1359,7 +1366,7 @@ Summary: ItemScope.Builder
        - setHeight(String height) + setHeight(String height)
        The height of the media object.
        @@ -1377,7 +1384,7 @@ Summary: ItemScope.Builder
        - setId(String id) + setId(String id)
        An identifier for the target.
        @@ -1395,7 +1402,7 @@ Summary: ItemScope.Builder
        - setImage(String image) + setImage(String image)
        A URL to the image that represents this result.
        @@ -1485,7 +1492,7 @@ Summary: ItemScope.Builder
        - setName(String name) + setName(String name)
        The name of the result.
        @@ -1521,7 +1528,7 @@ Summary: ItemScope.Builder
        - setPerformers(List<ItemScope> performers) + setPerformers(List<ItemScope> performers)
        The main performer or performers of the event-for example, a presenter, musician, or actor.
        @@ -1540,7 +1547,7 @@ Summary: ItemScope.Builder
        - setPlayerType(String playerType) + setPlayerType(String playerType)
        Player type that is required.
        @@ -1558,7 +1565,7 @@ Summary: ItemScope.Builder
        - setPostOfficeBoxNumber(String postOfficeBoxNumber) + setPostOfficeBoxNumber(String postOfficeBoxNumber)
        Post office box number.
        @@ -1576,7 +1583,7 @@ Summary: ItemScope.Builder
        - setPostalCode(String postalCode) + setPostalCode(String postalCode)
        Postal code.
        @@ -1594,7 +1601,7 @@ Summary: ItemScope.Builder
        - setRatingValue(String ratingValue) + setRatingValue(String ratingValue)
        Rating value.
        @@ -1630,7 +1637,7 @@ Summary: ItemScope.Builder
        - setStartDate(String startDate) + setStartDate(String startDate)
        The start date and time of the event (in ISO 8601 date format).
        @@ -1648,7 +1655,7 @@ Summary: ItemScope.Builder
        - setStreetAddress(String streetAddress) + setStreetAddress(String streetAddress)
        Street address.
        @@ -1666,7 +1673,7 @@ Summary: ItemScope.Builder
        - setText(String text) + setText(String text)
        The text that is the result of the app activity.
        @@ -1702,7 +1709,7 @@ Summary: ItemScope.Builder
        - setThumbnailUrl(String thumbnailUrl) + setThumbnailUrl(String thumbnailUrl)
        A URL to a thumbnail image that represents this result.
        @@ -1720,7 +1727,7 @@ Summary: ItemScope.Builder
        - setTickerSymbol(String tickerSymbol) + setTickerSymbol(String tickerSymbol)
        The exchange traded instrument associated with a Corporation object.
        @@ -1738,7 +1745,7 @@ Summary: ItemScope.Builder
        - setType(String type) + setType(String type)
        The schema.org URL that best describes the referenced target and matches the type of moment.
        @@ -1757,7 +1764,7 @@ Summary: ItemScope.Builder
        - setUrl(String url) + setUrl(String url)
        The URL that points to the result object.
        @@ -1775,7 +1782,7 @@ Summary: ItemScope.Builder
        - setWidth(String width) + setWidth(String width)
        The width of the media object.
        @@ -1793,7 +1800,7 @@ Summary: ItemScope.Builder
        - setWorstRating(String worstRating) + setWorstRating(String worstRating)
        Worst possible rating value that a result might obtain.
        @@ -1823,7 +1830,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1861,7 +1868,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1954,7 +1961,7 @@ From class - String + String toString() @@ -2168,7 +2175,7 @@ From class ItemScope.Builder setAdditionalName - (List<String> additionalName) + (List<String> additionalName)
        @@ -2228,7 +2235,7 @@ From class ItemScope.Builder setAddressCountry - (String addressCountry) + (String addressCountry)
        @@ -2258,7 +2265,7 @@ From class ItemScope.Builder setAddressLocality - (String addressLocality) + (String addressLocality)
        @@ -2288,7 +2295,7 @@ From class ItemScope.Builder setAddressRegion - (String addressRegion) + (String addressRegion)
        @@ -2318,7 +2325,7 @@ From class ItemScope.Builder setAssociated_media - (List<ItemScope> associated_media) + (List<ItemScope> associated_media)
        @@ -2378,7 +2385,7 @@ From class ItemScope.Builder setAttendees - (List<ItemScope> attendees) + (List<ItemScope> attendees)
        @@ -2438,7 +2445,7 @@ From class ItemScope.Builder setAuthor - (List<ItemScope> author) + (List<ItemScope> author)
        @@ -2469,7 +2476,7 @@ From class ItemScope.Builder setBestRating - (String bestRating) + (String bestRating)
        @@ -2501,7 +2508,7 @@ From class ItemScope.Builder setBirthDate - (String birthDate) + (String birthDate)
        @@ -2561,7 +2568,7 @@ From class ItemScope.Builder setCaption - (String caption) + (String caption)
        @@ -2591,7 +2598,7 @@ From class ItemScope.Builder setContentSize - (String contentSize) + (String contentSize)
        @@ -2621,7 +2628,7 @@ From class ItemScope.Builder setContentUrl - (String contentUrl) + (String contentUrl)
        @@ -2651,7 +2658,7 @@ From class ItemScope.Builder setContributor - (List<ItemScope> contributor) + (List<ItemScope> contributor)
        @@ -2681,7 +2688,7 @@ From class ItemScope.Builder setDateCreated - (String dateCreated) + (String dateCreated)
        @@ -2711,7 +2718,7 @@ From class ItemScope.Builder setDateModified - (String dateModified) + (String dateModified)
        @@ -2741,7 +2748,7 @@ From class ItemScope.Builder setDatePublished - (String datePublished) + (String datePublished)
        @@ -2774,7 +2781,7 @@ From class ItemScope.Builder setDescription - (String description) + (String description)
        @@ -2804,7 +2811,7 @@ From class ItemScope.Builder setDuration - (String duration) + (String duration)
        @@ -2834,7 +2841,7 @@ From class ItemScope.Builder setEmbedUrl - (String embedUrl) + (String embedUrl)
        @@ -2865,7 +2872,7 @@ From class ItemScope.Builder setEndDate - (String endDate) + (String endDate)
        @@ -2895,7 +2902,7 @@ From class ItemScope.Builder setFamilyName - (String familyName) + (String familyName)
        @@ -2925,7 +2932,7 @@ From class ItemScope.Builder setGender - (String gender) + (String gender)
        @@ -2985,7 +2992,7 @@ From class ItemScope.Builder setGivenName - (String givenName) + (String givenName)
        @@ -3015,7 +3022,7 @@ From class ItemScope.Builder setHeight - (String height) + (String height)
        @@ -3045,7 +3052,7 @@ From class ItemScope.Builder setId - (String id) + (String id)
        @@ -3077,7 +3084,7 @@ From class ItemScope.Builder setImage - (String image) + (String image)
        @@ -3229,7 +3236,7 @@ From class ItemScope.Builder setName - (String name) + (String name)
        @@ -3290,7 +3297,7 @@ From class ItemScope.Builder setPerformers - (List<ItemScope> performers) + (List<ItemScope> performers)
        @@ -3321,7 +3328,7 @@ From class ItemScope.Builder setPlayerType - (String playerType) + (String playerType)
        @@ -3351,7 +3358,7 @@ From class ItemScope.Builder setPostOfficeBoxNumber - (String postOfficeBoxNumber) + (String postOfficeBoxNumber)
        @@ -3381,7 +3388,7 @@ From class ItemScope.Builder setPostalCode - (String postalCode) + (String postalCode)
        @@ -3411,7 +3418,7 @@ From class ItemScope.Builder setRatingValue - (String ratingValue) + (String ratingValue)
        @@ -3471,7 +3478,7 @@ From class ItemScope.Builder setStartDate - (String startDate) + (String startDate)
        @@ -3501,7 +3508,7 @@ From class ItemScope.Builder setStreetAddress - (String streetAddress) + (String streetAddress)
        @@ -3531,7 +3538,7 @@ From class ItemScope.Builder setText - (String text) + (String text)
        @@ -3592,7 +3599,7 @@ From class ItemScope.Builder setThumbnailUrl - (String thumbnailUrl) + (String thumbnailUrl)
        @@ -3622,7 +3629,7 @@ From class ItemScope.Builder setTickerSymbol - (String tickerSymbol) + (String tickerSymbol)
        @@ -3655,7 +3662,7 @@ From class ItemScope.Builder setType - (String type) + (String type)
        @@ -3686,7 +3693,7 @@ From class ItemScope.Builder setUrl - (String url) + (String url)
        @@ -3717,7 +3724,7 @@ From class ItemScope.Builder setWidth - (String width) + (String width)
        @@ -3747,7 +3754,7 @@ From class ItemScope.Builder setWorstRating - (String worstRating) + (String worstRating)
        diff --git a/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.html b/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.html index bef3573b37bfb2fa53c55993e6ab72ed4c661570..33e8491181e2d0269945b988dcb14220d6c92ba1 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.html +++ b/docs/html/reference/com/google/android/gms/plus/model/moments/ItemScope.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + ItemScope | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getAdditionalName() @@ -854,7 +861,7 @@ Summary: - String + String getAddressCountry() @@ -872,7 +879,7 @@ Summary: - String + String getAddressLocality() @@ -890,7 +897,7 @@ Summary: - String + String getAddressRegion() @@ -908,7 +915,7 @@ Summary: - List<ItemScope> + List<ItemScope> getAssociated_media() @@ -944,7 +951,7 @@ Summary: - List<ItemScope> + List<ItemScope> getAttendees() @@ -980,7 +987,7 @@ Summary: - List<ItemScope> + List<ItemScope> getAuthor() @@ -998,7 +1005,7 @@ Summary: - String + String getBestRating() @@ -1016,7 +1023,7 @@ Summary: - String + String getBirthDate() @@ -1052,7 +1059,7 @@ Summary: - String + String getCaption() @@ -1070,7 +1077,7 @@ Summary: - String + String getContentSize() @@ -1088,7 +1095,7 @@ Summary: - String + String getContentUrl() @@ -1106,7 +1113,7 @@ Summary: - List<ItemScope> + List<ItemScope> getContributor() @@ -1124,7 +1131,7 @@ Summary: - String + String getDateCreated() @@ -1142,7 +1149,7 @@ Summary: - String + String getDateModified() @@ -1160,7 +1167,7 @@ Summary: - String + String getDatePublished() @@ -1178,7 +1185,7 @@ Summary: - String + String getDescription() @@ -1196,7 +1203,7 @@ Summary: - String + String getDuration() @@ -1214,7 +1221,7 @@ Summary: - String + String getEmbedUrl() @@ -1232,7 +1239,7 @@ Summary: - String + String getEndDate() @@ -1250,7 +1257,7 @@ Summary: - String + String getFamilyName() @@ -1268,7 +1275,7 @@ Summary: - String + String getGender() @@ -1304,7 +1311,7 @@ Summary: - String + String getGivenName() @@ -1322,7 +1329,7 @@ Summary: - String + String getHeight() @@ -1340,7 +1347,7 @@ Summary: - String + String getId() @@ -1358,7 +1365,7 @@ Summary: - String + String getImage() @@ -1448,7 +1455,7 @@ Summary: - String + String getName() @@ -1484,7 +1491,7 @@ Summary: - List<ItemScope> + List<ItemScope> getPerformers() @@ -1502,7 +1509,7 @@ Summary: - String + String getPlayerType() @@ -1520,7 +1527,7 @@ Summary: - String + String getPostOfficeBoxNumber() @@ -1538,7 +1545,7 @@ Summary: - String + String getPostalCode() @@ -1556,7 +1563,7 @@ Summary: - String + String getRatingValue() @@ -1592,7 +1599,7 @@ Summary: - String + String getStartDate() @@ -1610,7 +1617,7 @@ Summary: - String + String getStreetAddress() @@ -1628,7 +1635,7 @@ Summary: - String + String getText() @@ -1664,7 +1671,7 @@ Summary: - String + String getThumbnailUrl() @@ -1682,7 +1689,7 @@ Summary: - String + String getTickerSymbol() @@ -1700,7 +1707,7 @@ Summary: - String + String getType() @@ -1718,7 +1725,7 @@ Summary: - String + String getUrl() @@ -1736,7 +1743,7 @@ Summary: - String + String getWidth() @@ -1754,7 +1761,7 @@ Summary: - String + String getWorstRating() @@ -2894,7 +2901,7 @@ From interface abstract - List<String> + List<String> getAdditionalName () @@ -2954,7 +2961,7 @@ From interface abstract - String + String getAddressCountry () @@ -2984,7 +2991,7 @@ From interface abstract - String + String getAddressLocality () @@ -3014,7 +3021,7 @@ From interface abstract - String + String getAddressRegion () @@ -3044,7 +3051,7 @@ From interface abstract - List<ItemScope> + List<ItemScope> getAssociated_media () @@ -3104,7 +3111,7 @@ From interface abstract - List<ItemScope> + List<ItemScope> getAttendees () @@ -3164,7 +3171,7 @@ From interface abstract - List<ItemScope> + List<ItemScope> getAuthor () @@ -3195,7 +3202,7 @@ From interface abstract - String + String getBestRating () @@ -3227,7 +3234,7 @@ From interface abstract - String + String getBirthDate () @@ -3287,7 +3294,7 @@ From interface abstract - String + String getCaption () @@ -3317,7 +3324,7 @@ From interface abstract - String + String getContentSize () @@ -3347,7 +3354,7 @@ From interface abstract - String + String getContentUrl () @@ -3377,7 +3384,7 @@ From interface abstract - List<ItemScope> + List<ItemScope> getContributor () @@ -3407,7 +3414,7 @@ From interface abstract - String + String getDateCreated () @@ -3437,7 +3444,7 @@ From interface abstract - String + String getDateModified () @@ -3467,7 +3474,7 @@ From interface abstract - String + String getDatePublished () @@ -3500,7 +3507,7 @@ From interface abstract - String + String getDescription () @@ -3530,7 +3537,7 @@ From interface abstract - String + String getDuration () @@ -3560,7 +3567,7 @@ From interface abstract - String + String getEmbedUrl () @@ -3591,7 +3598,7 @@ From interface abstract - String + String getEndDate () @@ -3621,7 +3628,7 @@ From interface abstract - String + String getFamilyName () @@ -3651,7 +3658,7 @@ From interface abstract - String + String getGender () @@ -3711,7 +3718,7 @@ From interface abstract - String + String getGivenName () @@ -3741,7 +3748,7 @@ From interface abstract - String + String getHeight () @@ -3771,7 +3778,7 @@ From interface abstract - String + String getId () @@ -3803,7 +3810,7 @@ From interface abstract - String + String getImage () @@ -3954,7 +3961,7 @@ From interface abstract - String + String getName () @@ -4015,7 +4022,7 @@ From interface abstract - List<ItemScope> + List<ItemScope> getPerformers () @@ -4045,7 +4052,7 @@ From interface abstract - String + String getPlayerType () @@ -4075,7 +4082,7 @@ From interface abstract - String + String getPostOfficeBoxNumber () @@ -4105,7 +4112,7 @@ From interface abstract - String + String getPostalCode () @@ -4135,7 +4142,7 @@ From interface abstract - String + String getRatingValue () @@ -4195,7 +4202,7 @@ From interface abstract - String + String getStartDate () @@ -4225,7 +4232,7 @@ From interface abstract - String + String getStreetAddress () @@ -4255,7 +4262,7 @@ From interface abstract - String + String getText () @@ -4316,7 +4323,7 @@ From interface abstract - String + String getThumbnailUrl () @@ -4346,7 +4353,7 @@ From interface abstract - String + String getTickerSymbol () @@ -4379,7 +4386,7 @@ From interface abstract - String + String getType () @@ -4409,7 +4416,7 @@ From interface abstract - String + String getUrl () @@ -4440,7 +4447,7 @@ From interface abstract - String + String getWidth () @@ -4470,7 +4477,7 @@ From interface abstract - String + String getWorstRating () diff --git a/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.Builder.html b/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.Builder.html index 48200d832fe6aef03e9ac4e9aab8fe1ce4ec49f3..77188b92b1154e4c942fd4f29e8d93db0c3d6d69 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.Builder.html +++ b/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.Builder.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Moment.Builder | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        - setId(String id) + setId(String id)
        The moment ID.
        @@ -873,7 +880,7 @@ Summary: Moment.Builder
        - setStartDate(String startDate) + setStartDate(String startDate)
        Time stamp of when the action occurred in RFC3339 format.
        @@ -909,7 +916,7 @@ Summary: Moment.Builder
        - setType(String type) + setType(String type)
        The Google schema for the type of moment to write.
        @@ -939,7 +946,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -977,7 +984,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1070,7 +1077,7 @@ From class - String + String toString() @@ -1254,7 +1261,7 @@ From class Moment.Builder setId - (String id) + (String id)
        @@ -1315,7 +1322,7 @@ From class Moment.Builder setStartDate - (String startDate) + (String startDate)
        @@ -1375,7 +1382,7 @@ From class Moment.Builder setType - (String type) + (String type)
        diff --git a/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.html b/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.html index eafe059c8448434b12418f2dee8f0988935666d8..ac8d3ffba7f9400b1507cdbe8385c23f9861a1e9 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.html +++ b/docs/html/reference/com/google/android/gms/plus/model/moments/Moment.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Moment | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getId() @@ -836,7 +843,7 @@ Summary: - String + String getStartDate() @@ -872,7 +879,7 @@ Summary: - String + String getType() @@ -1100,7 +1107,7 @@ From interface abstract - String + String getId () @@ -1161,7 +1168,7 @@ From interface abstract - String + String getStartDate () @@ -1221,7 +1228,7 @@ From interface abstract - String + String getType () diff --git a/docs/html/reference/com/google/android/gms/plus/model/moments/MomentBuffer.html b/docs/html/reference/com/google/android/gms/plus/model/moments/MomentBuffer.html index 717689ba81e4b799f303a08e8c4e9ce3d6f9e5ce..9ca63f39d8d88e2732f0a2b1532bd6bab6230af1 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/moments/MomentBuffer.html +++ b/docs/html/reference/com/google/android/gms/plus/model/moments/MomentBuffer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + MomentBuffer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        iterator() @@ -975,7 +982,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1013,7 +1020,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1106,7 +1113,7 @@ From class - String + String toString() @@ -1177,7 +1184,7 @@ From class class="jd-expando-trigger-img" /> From interface - java.lang.Iterable + java.lang.Iterable
        Iterator<T> + Iterator<T>
        iterator() diff --git a/docs/html/reference/com/google/android/gms/plus/model/moments/package-summary.html b/docs/html/reference/com/google/android/gms/plus/model/moments/package-summary.html index bf35ac5395eb9b1bea4cb1cae02dd4b4a48d9e1b..35f1ecc0fc71fccda87a5229a637991291e4ce40 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/moments/package-summary.html +++ b/docs/html/reference/com/google/android/gms/plus/model/moments/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + com.google.android.gms.plus.model.moments | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getUrl() @@ -1039,7 +1046,7 @@ From interface abstract - String + String getUrl () diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.Layout.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.Layout.html index ed23f8d0671e9a6363a9731b8f40ce924ba6070c..cbc618f120592d9afa8d8d9d60fe480ca9ee37a1 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.Layout.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.Layout.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Cover.Layout | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -850,7 +857,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -943,7 +950,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.html index 82154b04186b1ef156ae95da9c6e76ca2a9a2c76..37f6e955ff267d7a116fe2d637cfbb79d81c1599 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Cover.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Cover | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -864,7 +871,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -957,7 +964,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Emails.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Emails.html index 59311841d50b453a8c2a2e0a6ac301707f3c0bc7..c417d888e4cd430d315077b17db890b4aa806dbb 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Emails.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Emails.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Emails | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getValue() @@ -1061,7 +1068,7 @@ From interface abstract - String + String getValue () diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Gender.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Gender.html index d687f6f3be4530acdb172b3da928c45a52f8f9fa..bfbdb00260dcbd9619fedf3619b77020ff8292b4 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Gender.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Gender.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Gender | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -864,7 +871,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -957,7 +964,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Image.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Image.html index dc62c46c8e2b91c437f60577727c1ae562a8451f..5a0d367444e06b36ef170989f7972bfe744c0ce0 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Image.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Image.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Image | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getUrl() @@ -937,7 +944,7 @@ From interface abstract - String + String getUrl () diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Name.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Name.html index 1e75a78e9468f43628fc825cc13fb6c594fcfef6..7bb7657a69ff54f53c54a838b4d9126b5acdefee 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Name.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Name.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Name | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getFamilyName() @@ -799,7 +806,7 @@ Summary: - String + String getFormatted() @@ -817,7 +824,7 @@ Summary: - String + String getGivenName() @@ -835,7 +842,7 @@ Summary: - String + String getHonorificPrefix() @@ -853,7 +860,7 @@ Summary: - String + String getHonorificSuffix() @@ -871,7 +878,7 @@ Summary: - String + String getMiddleName() @@ -1117,7 +1124,7 @@ From interface abstract - String + String getFamilyName () @@ -1147,7 +1154,7 @@ From interface abstract - String + String getFormatted () @@ -1177,7 +1184,7 @@ From interface abstract - String + String getGivenName () @@ -1207,7 +1214,7 @@ From interface abstract - String + String getHonorificPrefix () @@ -1237,7 +1244,7 @@ From interface abstract - String + String getHonorificSuffix () @@ -1267,7 +1274,7 @@ From interface abstract - String + String getMiddleName () diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.ObjectType.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.ObjectType.html index 3bada21c61c77685f61dbb686dbda0cde57f2491..e2ffd607470f3d9283287c5961cfbf881960acab 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.ObjectType.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.ObjectType.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.ObjectType | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -857,7 +864,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -950,7 +957,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.OrderBy.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.OrderBy.html index 114858083f8674eebb626e9de721effe1a8bf1f2..1970270dbc52137995d709ea5d63440fd882b4b9 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.OrderBy.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.OrderBy.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.OrderBy | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -857,7 +864,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -950,7 +957,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Organizations.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Organizations.html index 6ced2518ad161adbbe9798b62dbf34a422e56227..c96a615e1e67d74d4eebe5b641674a9914368300 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Organizations.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Organizations.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Organizations | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getDepartment() @@ -818,7 +825,7 @@ Summary: - String + String getDescription() @@ -836,12 +843,12 @@ Summary: - String + String getEndDate() -
        The date the person left this organization.
        +
        The date that the person left this organization.
        getLocation() @@ -872,7 +879,7 @@ Summary: - String + String getName() @@ -890,12 +897,12 @@ Summary: - String + String getStartDate() -
        The date the person joined this organization.
        +
        The date that the person joined this organization.
        getTitle() @@ -1111,8 +1118,8 @@ Summary: isPrimary() -
        If "true", indicates this organization is the person's primary one (typically interpreted as - current one).
        +
        If "true", indicates this organization is the person's primary one, which is typically + interpreted as the current one.
        getValue() @@ -969,7 +976,7 @@ From interface abstract - String + String getValue () diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.RelationshipStatus.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.RelationshipStatus.html index 9221a55363c7853362efcfe183b0e60a7782d341..8707d81d65ac3854f6a94b3863d00784cb1adb85 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.RelationshipStatus.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.RelationshipStatus.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.RelationshipStatus | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        clone() @@ -906,7 +913,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -999,7 +1006,7 @@ From class - String + String toString() diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.Type.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.Type.html index 70d04994fafc8c767727bf6b1595c659aa10b830..2a94220d8b01b889a5ed9d6b4b230bf9fc12ab81 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.Type.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.Type.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Urls.Type | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        intBLOG - This constant is deprecated. - URL for blog (not returned). -
        int CONTRIBUTOR URL for which this person is a contributor to.
        intHOME - This constant is deprecated. - URL for home (not returned). -
        int OTHER
        intPROFILE - This constant is deprecated. - URL for profile (not returned). -
        int WEBSITE URL for this Google+ Page's primary website.
        intWORK - This constant is deprecated. - URL for work (not returned). -
        @@ -873,7 +840,7 @@ Summary: class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -911,7 +878,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1004,7 +971,7 @@ From class - String + String toString() @@ -1095,49 +1062,6 @@ From class - - -
        -

        - - public - static - final - int - - BLOG -

        -
        - - - - -
        -
        -

        -

        - This constant is deprecated.
        - URL for blog (not returned). - -

        -

        - - -
        - Constant Value: - - - 2 - (0x00000002) - - -
        - -
        -
        - - -
        @@ -1177,49 +1101,6 @@ From class - - -
        -

        - - public - static - final - int - - HOME -

        -
        - - - - -
        -
        -

        -

        - This constant is deprecated.
        - URL for home (not returned). - -

        -

        - - -
        - Constant Value: - - - 0 - (0x00000000) - - -
        - -
        -
        - - -
        @@ -1298,49 +1179,6 @@ From class - - -
        -

        - - public - static - final - int - - PROFILE -

        -
        - - - - -
        -
        -

        -

        - This constant is deprecated.
        - URL for profile (not returned). - -

        -

        - - -
        - Constant Value: - - - 3 - (0x00000003) - - -
        - -
        -
        - - -
        @@ -1380,49 +1218,6 @@ From class - - -
        -

        - - public - static - final - int - - WORK -

        -
        - - - - -
        -
        -

        -

        - This constant is deprecated.
        - URL for work (not returned). - -

        -

        - - -
        - Constant Value: - - - 1 - (0x00000001) - - -
        - -
        -
        - - - diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.html index aed3810918ae6c4db53123cd3c4946be59f936a4..cffb8b611ff5a1b7bbedc42a89cc8ac85373b8eb 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.Urls.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person.Urls | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getLabel() @@ -836,7 +843,7 @@ Summary: - String + String getValue() @@ -872,27 +879,6 @@ Summary: - boolean - - hasPrimary() - -
        - This method is deprecated. - Returns false (removed). -
        - -
        - abstract - - - - boolean @@ -904,7 +890,7 @@ Summary: -
        abstract @@ -921,27 +907,6 @@ Summary:
        - abstract - - - - - boolean - - isPrimary() - -
        - This method is deprecated. - Returns false (removed). -
        - -
        @@ -1070,7 +1035,7 @@ From interface abstract - String + String getLabel () @@ -1113,11 +1078,11 @@ From interface
        -

        The type of URL. Possible values are: +

        The type of URL. Possible values include, but are not limited to, the following values: - "otherProfile" - URL for another profile. - - "contributor" - URL for which this person is a contributor to. + - "contributor" - URL to a site for which this person is a contributor. - "website" - URL for this Google+ Page's primary website. - - "other" - Other. + - "other" - Other URL.

        @@ -1134,7 +1099,7 @@ From interface abstract - String + String getValue () @@ -1184,40 +1149,6 @@ From interface
        - - -
        -

        - - public - - - abstract - - boolean - - hasPrimary - () -

        -
        -
        - - - -
        -
        -

        -

        - This method is deprecated.
        - Returns false (removed). - -

        -

        - -
        -
        - -
        @@ -1278,40 +1209,6 @@ From interface
        - - -
        -

        - - public - - - abstract - - boolean - - isPrimary - () -

        -
        -
        - - - -
        -
        -

        -

        - This method is deprecated.
        - Returns false (removed). - -

        -

        - -
        -
        - - diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/Person.html b/docs/html/reference/com/google/android/gms/plus/model/people/Person.html index f00f2d667a25d895c38d33feb74d18a32cac06d8..872d7ab7223fe1ab33eecdde70ec66910c1ef294 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/Person.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/Person.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + Person | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        getAboutMe() @@ -985,7 +992,7 @@ Summary: - String + String getBirthday() @@ -1003,7 +1010,7 @@ Summary: - String + String getBraggingRights() @@ -1058,7 +1065,7 @@ Summary: - String + String getCurrentLocation() @@ -1076,12 +1083,12 @@ Summary: - String + String getDisplayName() -
        The name of this person, suitable for display.
        +
        The name of this person, which is suitable for display.
        getEmails() @@ -1130,7 +1137,7 @@ Summary: - String + String getId() @@ -1166,7 +1173,7 @@ Summary: - String + String getLanguage() @@ -1202,7 +1209,7 @@ Summary: - String + String getNickname() @@ -1238,7 +1245,7 @@ Summary: - List<Person.Organizations> + List<Person.Organizations> getOrganizations() @@ -1256,7 +1263,7 @@ Summary: - List<Person.PlacesLived> + List<Person.PlacesLived> getPlacesLived() @@ -1310,7 +1317,7 @@ Summary: - String + String getTagline() @@ -1328,7 +1335,7 @@ Summary: - String + String getUrl() @@ -1346,7 +1353,7 @@ Summary: - List<Person.Urls> + List<Person.Urls> getUrls() @@ -1544,27 +1551,6 @@ Summary: - boolean - - hasHasApp() - -
        - This method is deprecated. - Returns false (removed). -
        - -
        - abstract - - - - boolean @@ -1576,7 +1562,7 @@ Summary: -
        abstract @@ -1594,7 +1580,7 @@ Summary: -
        abstract @@ -1612,7 +1598,7 @@ Summary: -
        abstract @@ -1630,7 +1616,7 @@ Summary: -
        abstract @@ -1648,7 +1634,7 @@ Summary: -
        abstract @@ -1666,7 +1652,7 @@ Summary: -
        abstract @@ -1684,7 +1670,7 @@ Summary: -
        abstract @@ -1702,7 +1688,7 @@ Summary: -
        abstract @@ -1720,7 +1706,7 @@ Summary: -
        abstract @@ -1738,7 +1724,7 @@ Summary: -
        abstract @@ -1756,7 +1742,7 @@ Summary: -
        abstract @@ -1774,7 +1760,7 @@ Summary: -
        abstract @@ -1792,7 +1778,7 @@ Summary: -
        abstract @@ -1810,7 +1796,7 @@ Summary: -
        abstract @@ -1828,27 +1814,6 @@ Summary: -
        - abstract - - - - - boolean - - isHasApp() - -
        - This method is deprecated. - Returns false (removed). -
        - -
        abstract @@ -2012,7 +1977,7 @@ From interface abstract - String + String getAboutMe () @@ -2072,7 +2037,7 @@ From interface abstract - String + String getBirthday () @@ -2102,7 +2067,7 @@ From interface abstract - String + String getBraggingRights () @@ -2193,7 +2158,7 @@ From interface abstract - String + String getCurrentLocation () @@ -2223,7 +2188,7 @@ From interface abstract - String + String getDisplayName () @@ -2236,7 +2201,7 @@ From interface
        -

        The name of this person, suitable for display. +

        The name of this person, which is suitable for display.

        @@ -2253,7 +2218,7 @@ From interface abstract - List<Person.Emails> + List<Person.Emails> getEmails () @@ -2297,7 +2262,7 @@ From interface
        -

        The person's gender. Possible values are: +

        The person's gender. Possible values include, but are not limited to, the following values: - "male" - Male gender. - "female" - Female gender. - "other" - Other. @@ -2317,7 +2282,7 @@ From interface abstract - String + String getId () @@ -2377,7 +2342,7 @@ From interface abstract - String + String getLanguage () @@ -2437,7 +2402,7 @@ From interface abstract - String + String getNickname () @@ -2480,7 +2445,8 @@ From interface

        -

        Type of person within Google+. Possible values are: +

        Type of person within Google+. Possible values include, but are not limited to, the following + values: - "person" - represents an actual person. - "page" - represents a page.

        @@ -2499,7 +2465,7 @@ From interface abstract - List<Person.Organizations> + List<Person.Organizations> getOrganizations () @@ -2529,7 +2495,7 @@ From interface abstract - List<Person.PlacesLived> + List<Person.PlacesLived> getPlacesLived () @@ -2602,7 +2568,8 @@ From interface
        -

        The person's relationship status. Possible values are: +

        The person's relationship status. Possible values include, but are not limited to, the + following values: - "single" - Person is single. - "in_a_relationship" - Person is in a relationship. - "engaged" - Person is engaged. @@ -2628,7 +2595,7 @@ From interface abstract - String + String getTagline () @@ -2658,7 +2625,7 @@ From interface abstract - String + String getUrl () @@ -2688,7 +2655,7 @@ From interface abstract - List<Person.Urls> + List<Person.Urls> getUrls () @@ -3008,40 +2975,6 @@ From interface

        - - -
        -

        - - public - - - abstract - - boolean - - hasHasApp - () -

        -
        -
        - - - -
        -
        -

        -

        - This method is deprecated.
        - Returns false (removed). - -

        -

        - -
        -
        - -
        @@ -3492,40 +3425,6 @@ From interface
        - - -
        -

        - - public - - - abstract - - boolean - - isHasApp - () -

        -
        -
        - - - -
        -
        -

        -

        - This method is deprecated.
        - Returns false (removed). - -

        -

        - -
        -
        - -
        diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/PersonBuffer.html b/docs/html/reference/com/google/android/gms/plus/model/people/PersonBuffer.html index fbb223bc66b11a4584abff7817544176148c0eca..2de080883c5820715a35f96c81ac4d1b22320f6c 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/PersonBuffer.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/PersonBuffer.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + PersonBuffer | Android Developers @@ -305,6 +307,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -371,6 +374,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
        @@ -503,24 +507,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        java.lang.Objectjava.lang.Object
        iterator() @@ -975,7 +982,7 @@ From class class="jd-expando-trigger-img" /> From class - java.lang.Object + java.lang.Object
        Object + Object
        clone() @@ -1013,7 +1020,7 @@ From class boolean - equals(Object arg0) + equals(Object arg0)
        getClass() @@ -1106,7 +1113,7 @@ From class - String + String toString() @@ -1177,7 +1184,7 @@ From class class="jd-expando-trigger-img" /> From interface - java.lang.Iterable + java.lang.Iterable
        Iterator<T> + Iterator<T>
        iterator() diff --git a/docs/html/reference/com/google/android/gms/plus/model/people/package-summary.html b/docs/html/reference/com/google/android/gms/plus/model/people/package-summary.html index f567f020c12dbbaa9256c2432f923edc8e637f32..e296efdffc2bc13c0097c40da455d969e24d8ddd 100644 --- a/docs/html/reference/com/google/android/gms/plus/model/people/package-summary.html +++ b/docs/html/reference/com/google/android/gms/plus/model/people/package-summary.html @@ -70,6 +70,8 @@ + + @@ -77,8 +79,8 @@ - - + + com.google.android.gms.plus.model.people | Android Developers @@ -306,6 +308,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • +
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
      • @@ -372,6 +375,7 @@ onkeyup="return search_changed(event, false, '/')" />
      • Google Services
      • + @@ -504,24 +508,27 @@ onkeyup="return search_changed(event, false, '/')" /> Google Cloud Messaging
        Listener interface for when a collection of people are loaded. 
        PlusClient.OnPersonLoadedListener - This interface is deprecated. - See PlusClient.OnPeopleLoadedListener. - 
        PlusClient.OrderBy Constants to declare the order to return people in. 
        PlusOneButton.OnPlusOneClickListener A listener for +1 button clicks. 
        The +1 button to recommend a URL on Google+. 
        PlusOneButton.DefaultOnPlusOneClickListenerThis is an View.OnClickListener that will proxy clicks to an + attached PlusOneButton.OnPlusOneClickListener, or default to attempt to start + the intent using an Activity context. 
        PlusOneButtonWithPopup+1 button which shows confirmation messages in a PopupWindow. 
        PlusOneDummyViewA class used to statically generate dummy views in the event of an error retrieving + a PlusOneButton from the apk + 
        PlusShare Utility class for including resources in posts shared on Google+ through an ACTION_SEND intent. 
        PlusShare.Builder  
        com.google.android.gms.authContains classes for authenticating Google accounts.
        com.google.android.gms.games.multiplayerContains data classes for multiplayer functionality.
        + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        FormatPlane countLayout details
        {@link android.graphics.ImageFormat#JPEG JPEG}1Compressed data, so row and pixel strides are 0. To uncompress, use + * {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}. + *
        {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}3A luminance plane followed by the Cb and Cr chroma planes. + * The chroma planes have half the width and height of the luminance + * plane (4:2:0 subsampling). Each pixel sample in each plane has 8 bits. + * Each plane has its own row stride and pixel stride.
        + * + * @see android.graphics.ImageFormat + */ + public abstract int getFormat(); + + /** + * The width of the image in pixels. For formats where some color channels + * are subsampled, this is the width of the largest-resolution plane. + */ + public abstract int getWidth(); + + /** + * The height of the image in pixels. For formats where some color channels + * are subsampled, this is the height of the largest-resolution plane. + */ + public abstract int getHeight(); + + /** + * Get the timestamp associated with this frame. + *

        + * The timestamp is measured in nanoseconds, and is monotonically + * increasing. However, the zero point and whether the timestamp can be + * compared against other sources of time or images depend on the source of + * this image. + *

        + */ + public abstract long getTimestamp(); + + /** + * Get the array of pixel planes for this Image. The number of planes is + * determined by the format of the Image. + */ + public abstract Plane[] getPlanes(); + + /** + * Free up this frame for reuse. + *

        + * After calling this method, calling any methods on this {@code Image} will + * result in an {@link IllegalStateException}, and attempting to read from + * {@link ByteBuffer ByteBuffers} returned by an earlier + * {@link Plane#getBuffer} call will have undefined behavior. + *

        + */ + @Override + public abstract void close(); + + /** + *

        A single color plane of image data.

        + * + *

        The number and meaning of the planes in an Image are determined by the + * format of the Image.

        + * + *

        Once the Image has been closed, any access to the the plane's + * ByteBuffer will fail.

        + * + * @see #getFormat + */ + public static abstract class Plane { + /** + * @hide + */ + protected Plane() { + } + + /** + *

        The row stride for this color plane, in bytes.

        + * + *

        This is the distance between the start of two consecutive rows of + * pixels in the image. The row stride is always greater than 0.

        + */ + public abstract int getRowStride(); + /** + *

        The distance between adjacent pixel samples, in bytes.

        + * + *

        This is the distance between two consecutive pixel values in a row + * of pixels. It may be larger than the size of a single pixel to + * account for interleaved image data or padded formats. + * The pixel stride is always greater than 0.

        + */ + public abstract int getPixelStride(); + /** + *

        Get a direct {@link java.nio.ByteBuffer ByteBuffer} + * containing the frame data.

        + * + *

        In particular, the buffer returned will always have + * {@link java.nio.ByteBuffer#isDirect isDirect} return {@code true}, so + * the underlying data could be mapped as a pointer in JNI without doing + * any copies with {@code GetDirectBufferAddress}.

        + * + * @return the byte buffer containing the image data for this plane. + */ + public abstract ByteBuffer getBuffer(); + } + +} diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java new file mode 100644 index 0000000000000000000000000000000000000000..d454c42fced81c8e4dd238b33deab3d5a1da168a --- /dev/null +++ b/media/java/android/media/ImageReader.java @@ -0,0 +1,736 @@ +/* + * 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.media; + +import android.graphics.ImageFormat; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.Surface; + +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + *

        The ImageReader class allows direct application access to image data + * rendered into a {@link android.view.Surface}

        + * + *

        Several Android media API classes accept Surface objects as targets to + * render to, including {@link MediaPlayer}, {@link MediaCodec}, and + * {@link android.renderscript.Allocation RenderScript Allocations}. The image + * sizes and formats that can be used with each source vary, and should be + * checked in the documentation for the specific API.

        + * + *

        The image data is encapsulated in {@link Image} objects, and multiple such + * objects can be accessed at the same time, up to the number specified by the + * {@code maxImages} constructor parameter. New images sent to an ImageReader + * through its {@link Surface} are queued until accessed through the {@link #acquireLatestImage} + * or {@link #acquireNextImage} call. Due to memory limits, an image source will + * eventually stall or drop Images in trying to render to the Surface if the + * ImageReader does not obtain and release Images at a rate equal to the + * production rate.

        + */ +public class ImageReader implements AutoCloseable { + + /** + * Returned by nativeImageSetup when acquiring the image was successful. + */ + private static final int ACQUIRE_SUCCESS = 0; + /** + * Returned by nativeImageSetup when we couldn't acquire the buffer, + * because there were no buffers available to acquire. + */ + private static final int ACQUIRE_NO_BUFS = 1; + /** + * Returned by nativeImageSetup when we couldn't acquire the buffer + * because the consumer has already acquired {@maxImages} and cannot + * acquire more than that. + */ + private static final int ACQUIRE_MAX_IMAGES = 2; + + /** + *

        Create a new reader for images of the desired size and format.

        + * + *

        The {@code maxImages} parameter determines the maximum number of {@link Image} + * objects that can be be acquired from the {@code ImageReader} + * simultaneously. Requesting more buffers will use up more memory, so it is + * important to use only the minimum number necessary for the use case.

        + * + *

        The valid sizes and formats depend on the source of the image + * data.

        + * + * @param width + * The width in pixels of the Images that this reader will produce. + * @param height + * The height in pixels of the Images that this reader will produce. + * @param format + * The format of the Image that this reader will produce. This + * must be one of the {@link android.graphics.ImageFormat} or + * {@link android.graphics.PixelFormat} constants. Note that + * not all formats is supported, like ImageFormat.NV21. + * @param maxImages + * The maximum number of images the user will want to + * access simultaneously. This should be as small as possible to limit + * memory use. Once maxImages Images are obtained by the user, one of them + * has to be released before a new Image will become available for access + * through {@link #acquireLatestImage()} or {@link #acquireNextImage()}. + * Must be greater than 0. + * + * @see Image + */ + public static ImageReader newInstance(int width, int height, int format, int maxImages) { + return new ImageReader(width, height, format, maxImages); + } + + /** + * @hide + */ + protected ImageReader(int width, int height, int format, int maxImages) { + mWidth = width; + mHeight = height; + mFormat = format; + mMaxImages = maxImages; + + if (width < 1 || height < 1) { + throw new IllegalArgumentException( + "The image dimensions must be positive"); + } + if (mMaxImages < 1) { + throw new IllegalArgumentException( + "Maximum outstanding image count must be at least 1"); + } + + if (format == ImageFormat.NV21) { + throw new IllegalArgumentException( + "NV21 format is not supported"); + } + + mNumPlanes = getNumPlanesFromFormat(); + + nativeInit(new WeakReference(this), width, height, format, maxImages); + + mSurface = nativeGetSurface(); + } + + /** + * The width of each {@link Image}, in pixels. + * + *

        ImageReader guarantees that all Images acquired from ImageReader (for example, with + * {@link #acquireNextImage}) will have the same dimensions as specified in + * {@link #newInstance}.

        + * + * @return the width of an Image + */ + public int getWidth() { + return mWidth; + } + + /** + * The height of each {@link Image}, in pixels. + * + *

        ImageReader guarantees that all Images acquired from ImageReader (for example, with + * {@link #acquireNextImage}) will have the same dimensions as specified in + * {@link #newInstance}.

        + * + * @return the height of an Image + */ + public int getHeight() { + return mHeight; + } + + /** + * The {@link ImageFormat image format} of each Image. + * + *

        ImageReader guarantees that all {@link Image Images} acquired from ImageReader + * (for example, with {@link #acquireNextImage}) will have the same format as specified in + * {@link #newInstance}.

        + * + * @return the format of an Image + * + * @see ImageFormat + */ + public int getImageFormat() { + return mFormat; + } + + /** + * Maximum number of images that can be acquired from the ImageReader by any time (for example, + * with {@link #acquireNextImage}). + * + *

        An image is considered acquired after it's returned by a function from ImageReader, and + * until the Image is {@link Image#close closed} to release the image back to the ImageReader. + *

        + * + *

        Attempting to acquire more than {@code maxImages} concurrently will result in the + * acquire function throwing a {@link IllegalStateException}. Furthermore, + * while the max number of images have been acquired by the ImageReader user, the producer + * enqueueing additional images may stall until at least one image has been released.

        + * + * @return Maximum number of images for this ImageReader. + * + * @see Image#close + */ + public int getMaxImages() { + return mMaxImages; + } + + /** + *

        Get a {@link Surface} that can be used to produce {@link Image Images} for this + * {@code ImageReader}.

        + * + *

        Until valid image data is rendered into this {@link Surface}, the + * {@link #acquireNextImage} method will return {@code null}. Only one source + * can be producing data into this Surface at the same time, although the + * same {@link Surface} can be reused with a different API once the first source is + * disconnected from the {@link Surface}.

        + * + * @return A {@link Surface} to use for a drawing target for various APIs. + */ + public Surface getSurface() { + return mSurface; + } + + /** + *

        + * Acquire the latest {@link Image} from the ImageReader's queue, dropping older + * {@link Image images}. Returns {@code null} if no new image is available. + *

        + *

        + * This operation will acquire all the images possible from the ImageReader, + * but {@link #close} all images that aren't the latest. This function is + * recommended to use over {@link #acquireNextImage} for most use-cases, as it's + * more suited for real-time processing. + *

        + *

        + * Note that {@link #getMaxImages maxImages} should be at least 2 for + * {@link #acquireLatestImage} to be any different than {@link #acquireNextImage} - + * discarding all-but-the-newest {@link Image} requires temporarily acquiring two + * {@link Image Images} at once. Or more generally, calling {@link #acquireLatestImage} + * with less than two images of margin, that is + * {@code (maxImages - currentAcquiredImages < 2)} will not discard as expected. + *

        + *

        + * This operation will fail by throwing an {@link IllegalStateException} if + * {@code maxImages} have been acquired with {@link #acquireLatestImage} or + * {@link #acquireNextImage}. In particular a sequence of {@link #acquireLatestImage} + * calls greater than {@link #getMaxImages} without calling {@link Image#close} in-between + * will exhaust the underlying queue. At such a time, {@link IllegalStateException} + * will be thrown until more images are + * released with {@link Image#close}. + *

        + * + * @return latest frame of image data, or {@code null} if no image data is available. + * @throws IllegalStateException if too many images are currently acquired + */ + public Image acquireLatestImage() { + Image image = acquireNextImage(); + if (image == null) { + return null; + } + try { + for (;;) { + Image next = acquireNextImageNoThrowISE(); + if (next == null) { + Image result = image; + image = null; + return result; + } + image.close(); + image = next; + } + } finally { + if (image != null) { + image.close(); + } + } + } + + /** + * Don't throw IllegalStateException if there are too many images acquired. + * + * @return Image if acquiring succeeded, or null otherwise. + * + * @hide + */ + public Image acquireNextImageNoThrowISE() { + SurfaceImage si = new SurfaceImage(); + return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null; + } + + /** + * Attempts to acquire the next image from the underlying native implementation. + * + *

        + * Note that unexpected failures will throw at the JNI level. + *

        + * + * @param si A blank SurfaceImage. + * @return One of the {@code ACQUIRE_*} codes that determine success or failure. + * + * @see #ACQUIRE_MAX_IMAGES + * @see #ACQUIRE_NO_BUFS + * @see #ACQUIRE_SUCCESS + */ + private int acquireNextSurfaceImage(SurfaceImage si) { + + int status = nativeImageSetup(si); + + switch (status) { + case ACQUIRE_SUCCESS: + si.createSurfacePlanes(); + si.setImageValid(true); + case ACQUIRE_NO_BUFS: + case ACQUIRE_MAX_IMAGES: + break; + default: + throw new AssertionError("Unknown nativeImageSetup return code " + status); + } + + return status; + } + + /** + *

        + * Acquire the next Image from the ImageReader's queue. Returns {@code null} if + * no new image is available. + *

        + * + *

        Warning: Consider using {@link #acquireLatestImage()} instead, as it will + * automatically release older images, and allow slower-running processing routines to catch + * up to the newest frame. Usage of {@link #acquireNextImage} is recommended for + * batch/background processing. Incorrectly using this function can cause images to appear + * with an ever-increasing delay, followed by a complete stall where no new images seem to + * appear. + *

        + * + *

        + * This operation will fail by throwing an {@link IllegalStateException} if + * {@code maxImages} have been acquired with {@link #acquireNextImage} or + * {@link #acquireLatestImage}. In particular a sequence of {@link #acquireNextImage} or + * {@link #acquireLatestImage} calls greater than {@link #getMaxImages maxImages} without + * calling {@link Image#close} in-between will exhaust the underlying queue. At such a time, + * {@link IllegalStateException} will be thrown until more images are released with + * {@link Image#close}. + *

        + * + * @return a new frame of image data, or {@code null} if no image data is available. + * @throws IllegalStateException if {@code maxImages} images are currently acquired + * @see #acquireLatestImage + */ + public Image acquireNextImage() { + SurfaceImage si = new SurfaceImage(); + int status = acquireNextSurfaceImage(si); + + switch (status) { + case ACQUIRE_SUCCESS: + return si; + case ACQUIRE_NO_BUFS: + return null; + case ACQUIRE_MAX_IMAGES: + throw new IllegalStateException( + String.format( + "maxImages (%d) has already been acquired, " + + "call #close before acquiring more.", mMaxImages)); + default: + throw new AssertionError("Unknown nativeImageSetup return code " + status); + } + } + + /** + *

        Return the frame to the ImageReader for reuse.

        + */ + private void releaseImage(Image i) { + if (! (i instanceof SurfaceImage) ) { + throw new IllegalArgumentException( + "This image was not produced by an ImageReader"); + } + SurfaceImage si = (SurfaceImage) i; + if (si.getReader() != this) { + throw new IllegalArgumentException( + "This image was not produced by this ImageReader"); + } + + si.clearSurfacePlanes(); + nativeReleaseImage(i); + si.setImageValid(false); + } + + /** + * Register a listener to be invoked when a new image becomes available + * from the ImageReader. + * + * @param listener + * The listener that will be run. + * @param handler + * The handler on which the listener should be invoked, or null + * if the listener should be invoked on the calling thread's looper. + * @throws IllegalArgumentException + * If no handler specified and the calling thread has no looper. + */ + public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) { + synchronized (mListenerLock) { + if (listener != null) { + Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); + if (looper == null) { + throw new IllegalArgumentException( + "handler is null but the current thread is not a looper"); + } + if (mListenerHandler == null || mListenerHandler.getLooper() != looper) { + mListenerHandler = new ListenerHandler(looper); + } + mListener = listener; + } else { + mListener = null; + mListenerHandler = null; + } + } + } + + /** + * Callback interface for being notified that a new image is available. + * + *

        + * The onImageAvailable is called per image basis, that is, callback fires for every new frame + * available from ImageReader. + *

        + */ + public interface OnImageAvailableListener { + /** + * Callback that is called when a new image is available from ImageReader. + * + * @param reader the ImageReader the callback is associated with. + * @see ImageReader + * @see Image + */ + void onImageAvailable(ImageReader reader); + } + + /** + * Free up all the resources associated with this ImageReader. + * + *

        + * After calling this method, this ImageReader can not be used. Calling + * any methods on this ImageReader and Images previously provided by + * {@link #acquireNextImage} or {@link #acquireLatestImage} + * will result in an {@link IllegalStateException}, and attempting to read from + * {@link ByteBuffer ByteBuffers} returned by an earlier + * {@link Image.Plane#getBuffer Plane#getBuffer} call will + * have undefined behavior. + *

        + */ + @Override + public void close() { + setOnImageAvailableListener(null, null); + nativeClose(); + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Only a subset of the formats defined in + * {@link android.graphics.ImageFormat ImageFormat} and + * {@link android.graphics.PixelFormat PixelFormat} are supported by + * ImageReader. When reading RGB data from a surface, the formats defined in + * {@link android.graphics.PixelFormat PixelFormat} can be used, when + * reading YUV, JPEG or raw sensor data (for example, from camera or video + * decoder), formats from {@link android.graphics.ImageFormat ImageFormat} + * are used. + */ + private int getNumPlanesFromFormat() { + switch (mFormat) { + case ImageFormat.YV12: + case ImageFormat.YUV_420_888: + case ImageFormat.NV21: + return 3; + case ImageFormat.NV16: + return 2; + case PixelFormat.RGB_565: + case PixelFormat.RGBA_8888: + case PixelFormat.RGBX_8888: + case PixelFormat.RGB_888: + case ImageFormat.JPEG: + case ImageFormat.YUY2: + case ImageFormat.Y8: + case ImageFormat.Y16: + case ImageFormat.RAW_SENSOR: + return 1; + default: + throw new UnsupportedOperationException( + String.format("Invalid format specified %d", mFormat)); + } + } + + /** + * Called from Native code when an Event happens. + * + * This may be called from an arbitrary Binder thread, so access to the ImageReader must be + * synchronized appropriately. + */ + private static void postEventFromNative(Object selfRef) { + @SuppressWarnings("unchecked") + WeakReference weakSelf = (WeakReference)selfRef; + final ImageReader ir = weakSelf.get(); + if (ir == null) { + return; + } + + final Handler handler; + synchronized (ir.mListenerLock) { + handler = ir.mListenerHandler; + } + if (handler != null) { + handler.sendEmptyMessage(0); + } + } + + + private final int mWidth; + private final int mHeight; + private final int mFormat; + private final int mMaxImages; + private final int mNumPlanes; + private final Surface mSurface; + + private final Object mListenerLock = new Object(); + private OnImageAvailableListener mListener; + private ListenerHandler mListenerHandler; + + /** + * This field is used by native code, do not access or modify. + */ + private long mNativeContext; + + /** + * This custom handler runs asynchronously so callbacks don't get queued behind UI messages. + */ + private final class ListenerHandler extends Handler { + public ListenerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + OnImageAvailableListener listener; + synchronized (mListenerLock) { + listener = mListener; + } + if (listener != null) { + listener.onImageAvailable(ImageReader.this); + } + } + } + + private class SurfaceImage extends android.media.Image { + public SurfaceImage() { + mIsImageValid = false; + } + + @Override + public void close() { + if (mIsImageValid) { + ImageReader.this.releaseImage(this); + } + } + + public ImageReader getReader() { + return ImageReader.this; + } + + @Override + public int getFormat() { + if (mIsImageValid) { + return ImageReader.this.mFormat; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public int getWidth() { + if (mIsImageValid) { + return ImageReader.this.mWidth; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public int getHeight() { + if (mIsImageValid) { + return ImageReader.this.mHeight; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public long getTimestamp() { + if (mIsImageValid) { + return mTimestamp; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public Plane[] getPlanes() { + if (mIsImageValid) { + // Shallow copy is fine. + return mPlanes.clone(); + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + protected final void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + private void setImageValid(boolean isValid) { + mIsImageValid = isValid; + } + + private boolean isImageValid() { + return mIsImageValid; + } + + private void clearSurfacePlanes() { + if (mIsImageValid) { + for (int i = 0; i < mPlanes.length; i++) { + if (mPlanes[i] != null) { + mPlanes[i].clearBuffer(); + mPlanes[i] = null; + } + } + } + } + + private void createSurfacePlanes() { + mPlanes = new SurfacePlane[ImageReader.this.mNumPlanes]; + for (int i = 0; i < ImageReader.this.mNumPlanes; i++) { + mPlanes[i] = nativeCreatePlane(i); + } + } + private class SurfacePlane extends android.media.Image.Plane { + // SurfacePlane instance is created by native code when a new SurfaceImage is created + private SurfacePlane(int index, int rowStride, int pixelStride) { + mIndex = index; + mRowStride = rowStride; + mPixelStride = pixelStride; + } + + @Override + public ByteBuffer getBuffer() { + if (SurfaceImage.this.isImageValid() == false) { + throw new IllegalStateException("Image is already released"); + } + if (mBuffer != null) { + return mBuffer; + } else { + mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex); + // Set the byteBuffer order according to host endianness (native order), + // otherwise, the byteBuffer order defaults to ByteOrder.BIG_ENDIAN. + return mBuffer.order(ByteOrder.nativeOrder()); + } + } + + @Override + public int getPixelStride() { + if (SurfaceImage.this.isImageValid()) { + return mPixelStride; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public int getRowStride() { + if (SurfaceImage.this.isImageValid()) { + return mRowStride; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + private void clearBuffer() { + mBuffer = null; + } + + final private int mIndex; + final private int mPixelStride; + final private int mRowStride; + + private ByteBuffer mBuffer; + } + + /** + * This field is used to keep track of native object and used by native code only. + * Don't modify. + */ + private long mLockedBuffer; + + /** + * This field is set by native code during nativeImageSetup(). + */ + private long mTimestamp; + + private SurfacePlane[] mPlanes; + private boolean mIsImageValid; + + private synchronized native ByteBuffer nativeImageGetBuffer(int idx); + private synchronized native SurfacePlane nativeCreatePlane(int idx); + } + + private synchronized native void nativeInit(Object weakSelf, int w, int h, + int fmt, int maxImgs); + private synchronized native void nativeClose(); + private synchronized native void nativeReleaseImage(Image i); + private synchronized native Surface nativeGetSurface(); + + /** + * @return A return code {@code ACQUIRE_*} + * + * @see #ACQUIRE_SUCCESS + * @see #ACQUIRE_NO_BUFS + * @see #ACQUIRE_MAX_IMAGES + */ + private synchronized native int nativeImageSetup(Image i); + + /** + * We use a class initializer to allow the native code to cache some + * field offsets. + */ + private static native void nativeClassInit(); + static { + System.loadLibrary("media_jni"); + nativeClassInit(); + } +} diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index d703642cf7fe79b0db2a3bcdbcf7ab0453a041d6..5175830dd0918128ab3bf18623fa8a8fe431a415 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -20,7 +20,9 @@ import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaCrypto; import android.media.MediaFormat; +import android.os.Bundle; import android.view.Surface; + import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Map; @@ -164,7 +166,8 @@ final public class MediaCodec { * * The following is a partial list of defined mime types and their semantics: *
          - *
        • "video/x-vnd.on2.vp8" - VPX video (i.e. video in .webm) + *
        • "video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm) + *
        • "video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm) *
        • "video/avc" - H.264/AVC video *
        • "video/mp4v-es" - MPEG4 video *
        • "video/3gpp" - H.263 video @@ -293,12 +296,36 @@ final public class MediaCodec { */ public native final void flush(); + /** + * Thrown when a crypto error occurs while queueing a secure input buffer. + */ public final static class CryptoException extends RuntimeException { public CryptoException(int errorCode, String detailMessage) { super(detailMessage); mErrorCode = errorCode; } + /** + * This indicates that no key has been set to perform the requested + * decrypt operation. + */ + public static final int ERROR_NO_KEY = 1; + + /** + * This indicates that the key used for decryption is no longer + * valid due to license term expiration. + */ + public static final int ERROR_KEY_EXPIRED = 2; + + /** + * This indicates that a required crypto resource was not able to be + * allocated while attempting the requested operation. + */ + public static final int ERROR_RESOURCE_BUSY = 3; + + /** + * Retrieve the error code associated with a CryptoException + */ public int getErrorCode() { return mErrorCode; } @@ -430,6 +457,9 @@ final public class MediaCodec { * @param presentationTimeUs The time at which this buffer should be rendered. * @param flags A bitmask of flags {@link #BUFFER_FLAG_SYNC_FRAME}, * {@link #BUFFER_FLAG_CODEC_CONFIG} or {@link #BUFFER_FLAG_END_OF_STREAM}. + * @throws CryptoException if an error occurs while attempting to decrypt the buffer. + * An error code associated with the exception helps identify the + * reason for the failure. */ public native final void queueSecureInputBuffer( int index, @@ -544,6 +574,52 @@ final public class MediaCodec { */ public native final String getName(); + /** + * Change a video encoder's target bitrate on the fly. The value is an + * Integer object containing the new bitrate in bps. + */ + public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate"; + + /** + * Temporarily suspend/resume encoding of input data. While suspended + * input data is effectively discarded instead of being fed into the + * encoder. This parameter really only makes sense to use with an encoder + * in "surface-input" mode, as the client code has no control over the + * input-side of the encoder in that case. + * The value is an Integer object containing the value 1 to suspend + * or the value 0 to resume. + */ + public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames"; + + /** + * Request that the encoder produce a sync frame "soon". + * Provide an Integer with the value 0. + */ + public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; + + /** + * Communicate additional parameter changes to the component instance. + */ + public final void setParameters(Bundle params) { + if (params == null) { + return; + } + + String[] keys = new String[params.size()]; + Object[] values = new Object[params.size()]; + + int i = 0; + for (final String key: params.keySet()) { + keys[i] = key; + values[i] = params.get(key); + ++i; + } + + setParameters(keys, values); + } + + private native final void setParameters(String[] keys, Object[] values); + /** * Get the codec info. If the codec was created by createDecoderByType * or createEncoderByType, what component is chosen is not known beforehand, diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index aeed7d4b5390623f6069eefb332967ee3d1bed4c..90c12c635c78e2df8a1d48aa4158afa12856fc20 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -72,7 +72,8 @@ public final class MediaCodecInfo { /** * Encapsulates the capabilities of a given codec component. * For example, what profile/level combinations it supports and what colorspaces - * it is capable of providing the decoded data in. + * it is capable of providing the decoded data in, as well as some + * codec-type specific capability flags. *

          You can get an instance for a given {@link MediaCodecInfo} object with * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type. */ @@ -139,6 +140,24 @@ public final class MediaCodecInfo { * OMX_COLOR_FORMATTYPE. */ public int[] colorFormats; + + private final static int FLAG_SupportsAdaptivePlayback = (1 << 0); + private int flags; + + /** + * video decoder only: codec supports seamless resolution changes. + */ + public final static String FEATURE_AdaptivePlayback = "adaptive-playback"; + + /** + * Query codec feature capabilities. + */ + public final boolean isFeatureSupported(String name) { + if (name.equals(FEATURE_AdaptivePlayback)) { + return (flags & FLAG_SupportsAdaptivePlayback) != 0; + } + return false; + } }; /** diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 7677d8a1ec39e2e1a8bc13958ab50404c7561ce9..6b278d490ba21ea200e9d0a3f968611d9a429e55 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -108,7 +108,19 @@ public final class MediaDrm { * @param uuid The UUID of the crypto scheme. */ public static final boolean isCryptoSchemeSupported(UUID uuid) { - return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid)); + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null); + } + + /** + * Query if the given scheme identified by its UUID is supported on + * this device, and whether the drm plugin is able to handle the + * media container format specified by mimeType. + * @param uuid The UUID of the crypto scheme. + * @param mimeType The MIME type of the media container, e.g. "video/mp4" + * or "video/webm" + */ + public static final boolean isCryptoSchemeSupported(UUID uuid, String mimeType) { + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType); } private static final byte[] getByteArrayFromUUID(UUID uuid) { @@ -124,7 +136,8 @@ public final class MediaDrm { return uuidBytes; } - private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid); + private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid, + String mimeType); /** * Instantiate a MediaDrm object @@ -273,6 +286,7 @@ public final class MediaDrm { * Open a new session with the MediaDrm object. A session ID is returned. * * @throws NotProvisionedException if provisioning is needed + * @throws ResourceBusyException if required resources are in use */ public native byte[] openSession() throws NotProvisionedException; @@ -379,6 +393,7 @@ public final class MediaDrm { * reprovisioning is required * @throws DeniedByServerException if the response indicates that the * server rejected the request + * @throws ResourceBusyException if required resources are in use */ public native byte[] provideKeyResponse(byte[] scope, byte[] response) throws NotProvisionedException, DeniedByServerException; diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java new file mode 100644 index 0000000000000000000000000000000000000000..07d91ace46a4646f1c11df107b6b346278eb7fd2 --- /dev/null +++ b/media/java/android/media/MediaFocusControl.java @@ -0,0 +1,2724 @@ +/* + * 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.media; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.app.PendingIntent.OnFinished; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.IBinder.DeathRecipient; +import android.provider.Settings; +import android.speech.RecognizerIntent; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.util.Slog; +import android.view.KeyEvent; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Stack; + +/** + * @hide + * + */ +public class MediaFocusControl implements OnFinished { + + private static final String TAG = "MediaFocusControl"; + + /** Debug remote control client/display feature */ + protected static final boolean DEBUG_RC = false; + /** Debug volumes */ + protected static final boolean DEBUG_VOL = false; + + /** Used to alter media button redirection when the phone is ringing. */ + private boolean mIsRinging = false; + + private final PowerManager.WakeLock mMediaEventWakeLock; + private final MediaEventHandler mEventHandler; + private final Context mContext; + private final ContentResolver mContentResolver; + private final VolumeController mVolumeController; + private final BroadcastReceiver mReceiver = new PackageIntentsReceiver(); + private final AppOpsManager mAppOps; + private final KeyguardManager mKeyguardManager; + private final AudioService mAudioService; + private final NotificationListenerObserver mNotifListenerObserver; + + protected MediaFocusControl(Looper looper, Context cntxt, + VolumeController volumeCtrl, AudioService as) { + mEventHandler = new MediaEventHandler(looper); + mContext = cntxt; + mContentResolver = mContext.getContentResolver(); + mVolumeController = volumeCtrl; + mAudioService = as; + + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); + mMainRemote = new RemotePlaybackState(-1, + AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC), + AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC)); + + // Register for phone state monitoring + TelephonyManager tmgr = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + + // Register for package addition/removal/change intent broadcasts + // for media button receiver persistence + IntentFilter pkgFilter = new IntentFilter(); + pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); + pkgFilter.addDataScheme("package"); + mContext.registerReceiver(mReceiver, pkgFilter); + + mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); + mKeyguardManager = + (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mNotifListenerObserver = new NotificationListenerObserver(); + + mHasRemotePlayback = false; + mMainRemoteIsActive = false; + postReevaluateRemote(); + } + + protected void dump(PrintWriter pw) { + dumpFocusStack(pw); + dumpRCStack(pw); + dumpRCCStack(pw); + dumpRCDList(pw); + } + + //========================================================================================== + // Management of RemoteControlDisplay registration permissions + //========================================================================================== + private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI = + Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + + private class NotificationListenerObserver extends ContentObserver { + + NotificationListenerObserver() { + super(mEventHandler); + mContentResolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) { + return; + } + if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); } + postReevaluateRemoteControlDisplays(); + } + } + + private final static int RCD_REG_FAILURE = 0; + private final static int RCD_REG_SUCCESS_PERMISSION = 1; + private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2; + + /** + * Checks a caller's authorization to register an IRemoteControlDisplay. + * Authorization is granted if one of the following is true: + *

            + *
          • the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission
          • + *
          • the caller's listener is one of the enabled notification listeners
          • + *
          + * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay + * registration. + */ + private int checkRcdRegistrationAuthorization(ComponentName listenerComp) { + // MEDIA_CONTENT_CONTROL permission check + if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MEDIA_CONTENT_CONTROL)) { + if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");} + return RCD_REG_SUCCESS_PERMISSION; + } + + // ENABLED_NOTIFICATION_LISTENERS settings check + if (listenerComp != null) { + // this call is coming from an app, can't use its identity to read secure settings + final long ident = Binder.clearCallingIdentity(); + try { + final int currentUser = ActivityManager.getCurrentUser(); + final String enabledNotifListeners = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (enabledNotifListeners != null) { + final String[] components = enabledNotifListeners.split(":"); + for (int i=0; i enabled list: " + enabledNotifListeners); } + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // check whether the "enable" status of each RCD with a notification listener + // has changed + final String[] enabledComponents; + if (enabledNotifListeners == null) { + enabledComponents = null; + } else { + enabledComponents = enabledNotifListeners.split(":"); + } + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = + (DisplayInfoForServer) displayIterator.next(); + if (di.mClientNotifListComp != null) { + boolean wasEnabled = di.mEnabled; + di.mEnabled = isComponentInStringArray(di.mClientNotifListComp, + enabledComponents); + if (wasEnabled != di.mEnabled){ + try { + // tell the RCD whether it's enabled + di.mRcDisplay.setEnabled(di.mEnabled); + // tell the RCCs about the change for this RCD + enableRemoteControlDisplayForClient_syncRcStack( + di.mRcDisplay, di.mEnabled); + // when enabling, refresh the information on the display + if (di.mEnabled) { + sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, + di.mArtworkExpectedWidth /*arg1*/, + di.mArtworkExpectedHeight/*arg2*/, + di.mRcDisplay /*obj*/, 0/*delay*/); + } + } catch (RemoteException e) { + Log.e(TAG, "Error en/disabling RCD: ", e); + } + } + } + } + } + } + } + + /** + * @param comp a non-null ComponentName + * @param enabledArray may be null + * @return + */ + private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) { + if (enabledArray == null || enabledArray.length == 0) { + if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); } + return false; + } + final String compString = comp.flattenToString(); + for (int i=0; i " + compString + " is enabled"); } + return true; + } + } + if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); } + return false; + } + + //========================================================================================== + // Internal event handling + //========================================================================================== + + // event handler messages + private static final int MSG_PERSIST_MEDIABUTTONRECEIVER = 0; + private static final int MSG_RCDISPLAY_CLEAR = 1; + private static final int MSG_RCDISPLAY_UPDATE = 2; + private static final int MSG_REEVALUATE_REMOTE = 3; + private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4; + private static final int MSG_RCC_NEW_VOLUME_OBS = 5; + private static final int MSG_PROMOTE_RCC = 6; + private static final int MSG_RCC_NEW_PLAYBACK_STATE = 7; + private static final int MSG_RCC_SEEK_REQUEST = 8; + private static final int MSG_RCC_UPDATE_METADATA = 9; + private static final int MSG_RCDISPLAY_INIT_INFO = 10; + private static final int MSG_REEVALUATE_RCD = 11; + + // sendMsg() flags + /** If the msg is already queued, replace it with this one. */ + private static final int SENDMSG_REPLACE = 0; + /** If the msg is already queued, ignore this one and leave the old. */ + private static final int SENDMSG_NOOP = 1; + /** If the msg is already queued, queue this one and leave the old. */ + private static final int SENDMSG_QUEUE = 2; + + private static void sendMsg(Handler handler, int msg, + int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { + + if (existingMsgPolicy == SENDMSG_REPLACE) { + handler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { + return; + } + + handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay); + } + + private class MediaEventHandler extends Handler { + MediaEventHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_PERSIST_MEDIABUTTONRECEIVER: + onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj ); + break; + + case MSG_RCDISPLAY_CLEAR: + onRcDisplayClear(); + break; + + case MSG_RCDISPLAY_UPDATE: + // msg.obj is guaranteed to be non null + onRcDisplayUpdate( (RemoteControlStackEntry) msg.obj, msg.arg1); + break; + + case MSG_REEVALUATE_REMOTE: + onReevaluateRemote(); + break; + + case MSG_RCC_NEW_PLAYBACK_INFO: + onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */, + ((Integer)msg.obj).intValue() /* value */); + break; + + case MSG_RCC_NEW_VOLUME_OBS: + onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, + (IRemoteVolumeObserver)msg.obj /* rvo */); + break; + + case MSG_RCC_NEW_PLAYBACK_STATE: + onNewPlaybackStateForRcc(msg.arg1 /* rccId */, + msg.arg2 /* state */, + (RccPlaybackState)msg.obj /* newState */); + break; + + case MSG_RCC_SEEK_REQUEST: + onSetRemoteControlClientPlaybackPosition( + msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */); + break; + + case MSG_RCC_UPDATE_METADATA: + onUpdateRemoteControlClientMetadata(msg.arg1 /*genId*/, msg.arg2 /*key*/, + (Rating) msg.obj /* value */); + break; + + case MSG_PROMOTE_RCC: + onPromoteRcc(msg.arg1); + break; + + case MSG_RCDISPLAY_INIT_INFO: + // msg.obj is guaranteed to be non null + onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/, + msg.arg1/*w*/, msg.arg2/*h*/); + break; + + case MSG_REEVALUATE_RCD: + onReevaluateRemoteControlDisplays(); + break; + } + } + } + + + //========================================================================================== + // AudioFocus + //========================================================================================== + + /* constant to identify focus stack entry that is used to hold the focus while the phone + * is ringing or during a call. Used by com.android.internal.telephony.CallManager when + * entering and exiting calls. + */ + protected final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls"; + + private final static Object mAudioFocusLock = new Object(); + + private final static Object mRingingLock = new Object(); + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + if (state == TelephonyManager.CALL_STATE_RINGING) { + //Log.v(TAG, " CALL_STATE_RINGING"); + synchronized(mRingingLock) { + mIsRinging = true; + } + } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK) + || (state == TelephonyManager.CALL_STATE_IDLE)) { + synchronized(mRingingLock) { + mIsRinging = false; + } + } + } + }; + + /** + * Discard the current audio focus owner. + * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign + * focus), remove it from the stack, and clear the remote control display. + */ + protected void discardAudioFocusOwner() { + synchronized(mAudioFocusLock) { + if (!mFocusStack.empty()) { + // notify the current focus owner it lost focus after removing it from stack + final FocusRequester exFocusOwner = mFocusStack.pop(); + exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS); + exFocusOwner.release(); + // clear RCD + synchronized(mRCStack) { + clearRemoteControlDisplay_syncAfRcs(); + } + } + } + } + + private void notifyTopOfAudioFocusStack() { + // notify the top of the stack it gained focus + if (!mFocusStack.empty()) { + if (canReassignAudioFocus()) { + mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); + } + } + } + + /** + * Focus is requested, propagate the associated loss throughout the stack. + * @param focusGain the new focus gain that will later be added at the top of the stack + */ + private void propagateFocusLossFromGain_syncAf(int focusGain) { + // going through the audio focus stack to signal new focus, traversing order doesn't + // matter as all entries respond to the same external focus gain + Iterator stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().handleExternalFocusGain(focusGain); + } + } + + private final Stack mFocusStack = new Stack(); + + /** + * Helper function: + * Display in the log the current entries in the audio focus stack + */ + private void dumpFocusStack(PrintWriter pw) { + pw.println("\nAudio Focus stack entries (last is top of stack):"); + synchronized(mAudioFocusLock) { + Iterator stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().dump(pw); + } + } + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock + * Remove a focus listener from the focus stack. + * @param clientToRemove the focus listener + * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding + * focus, notify the next item in the stack it gained focus. + */ + private void removeFocusStackEntry(String clientToRemove, boolean signal) { + // is the current top of the focus stack abandoning focus? (because of request, not death) + if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove)) + { + //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); + FocusRequester fr = mFocusStack.pop(); + fr.release(); + if (signal) { + // notify the new top of the stack it gained focus + notifyTopOfAudioFocusStack(); + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } else { + // focus is abandoned by a client that's not at the top of the stack, + // no need to update focus. + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + Iterator stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusRequester fr = (FocusRequester)stackIterator.next(); + if(fr.hasSameClient(clientToRemove)) { + Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + + clientToRemove); + stackIterator.remove(); + fr.release(); + } + } + } + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock + * Remove focus listeners from the focus stack for a particular client when it has died. + */ + private void removeFocusStackEntryForClient(IBinder cb) { + // is the owner of the audio focus part of the client to remove? + boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() && + mFocusStack.peek().hasSameBinder(cb); + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + Iterator stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusRequester fr = (FocusRequester)stackIterator.next(); + if(fr.hasSameBinder(cb)) { + Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb); + stackIterator.remove(); + // the client just died, no need to unlink to its death + } + } + if (isTopOfStackForClientToRemove) { + // we removed an entry at the top of the stack: + // notify the new top of the stack it gained focus. + notifyTopOfAudioFocusStack(); + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + + /** + * Helper function: + * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. + */ + private boolean canReassignAudioFocus() { + // focus requests are rejected during a phone call or when the phone is ringing + // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus + if (!mFocusStack.isEmpty() && mFocusStack.peek().hasSameClient(IN_VOICE_COMM_FOCUS_ID)) { + return false; + } + return true; + } + + /** + * Inner class to monitor audio focus client deaths, and remove them from the audio focus + * stack if necessary. + */ + protected class AudioFocusDeathHandler implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + + AudioFocusDeathHandler(IBinder cb) { + mCb = cb; + } + + public void binderDied() { + synchronized(mAudioFocusLock) { + Log.w(TAG, " AudioFocus audio focus client died"); + removeFocusStackEntryForClient(mCb); + } + } + + public IBinder getBinder() { + return mCb; + } + } + + protected int getCurrentAudioFocus() { + synchronized(mAudioFocusLock) { + if (mFocusStack.empty()) { + return AudioManager.AUDIOFOCUS_NONE; + } else { + return mFocusStack.peek().getGainRequest(); + } + } + } + + /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int) */ + protected int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb, + IAudioFocusDispatcher fd, String clientId, String callingPackageName) { + Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId); + // we need a valid binder callback for clients + if (!cb.pingBinder()) { + Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting."); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), + callingPackageName) != AppOpsManager.MODE_ALLOWED) { + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + synchronized(mAudioFocusLock) { + if (!canReassignAudioFocus()) { + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + // handle the potential premature death of the new holder of the focus + // (premature death == death before abandoning focus) + // Register for client death notification + AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); + try { + cb.linkToDeath(afdh, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) { + // if focus is already owned by this client and the reason for acquiring the focus + // hasn't changed, don't do anything + if (mFocusStack.peek().getGainRequest() == focusChangeHint) { + // unlink death handler so it can be gc'ed. + // linkToDeath() creates a JNI global reference preventing collection. + cb.unlinkToDeath(afdh, 0); + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + // the reason for the audio focus request has changed: remove the current top of + // stack and respond as if we had a new focus owner + FocusRequester fr = mFocusStack.pop(); + fr.release(); + } + + // focus requester might already be somewhere below in the stack, remove it + removeFocusStackEntry(clientId, false /* signal */); + + // propagate the focus change through the stack + if (!mFocusStack.empty()) { + propagateFocusLossFromGain_syncAf(focusChangeHint); + } + + // push focus requester at the top of the audio focus stack + mFocusStack.push(new FocusRequester(mainStreamType, focusChangeHint, fd, cb, + clientId, afdh, callingPackageName, Binder.getCallingUid())); + + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + }//synchronized(mAudioFocusLock) + + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + + /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener) */ + protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) { + Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); + try { + // this will take care of notifying the new focus owner if needed + synchronized(mAudioFocusLock) { + removeFocusStackEntry(clientId, true /*signal*/); + } + } catch (java.util.ConcurrentModificationException cme) { + // Catching this exception here is temporary. It is here just to prevent + // a crash seen when the "Silent" notification is played. This is believed to be fixed + // but this try catch block is left just to be safe. + Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme); + cme.printStackTrace(); + } + + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + + + protected void unregisterAudioFocusClient(String clientId) { + synchronized(mAudioFocusLock) { + removeFocusStackEntry(clientId, false); + } + } + + + //========================================================================================== + // RemoteControl + //========================================================================================== + /** + * No-op if the key code for keyEvent is not a valid media key + * (see {@link #isValidMediaKeyEvent(KeyEvent)}) + * @param keyEvent the key event to send + */ + protected void dispatchMediaKeyEvent(KeyEvent keyEvent) { + filterMediaKeyEvent(keyEvent, false /*needWakeLock*/); + } + + /** + * No-op if the key code for keyEvent is not a valid media key + * (see {@link #isValidMediaKeyEvent(KeyEvent)}) + * @param keyEvent the key event to send + */ + protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { + filterMediaKeyEvent(keyEvent, true /*needWakeLock*/); + } + + private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + // sanity check on the incoming key event + if (!isValidMediaKeyEvent(keyEvent)) { + Log.e(TAG, "not dispatching invalid media key event " + keyEvent); + return; + } + // event filtering for telephony + synchronized(mRingingLock) { + synchronized(mRCStack) { + if ((mMediaReceiverForCalls != null) && + (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) { + dispatchMediaKeyEventForCalls(keyEvent, needWakeLock); + return; + } + } + } + // event filtering based on voice-based interactions + if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) { + filterVoiceInputKeyEvent(keyEvent, needWakeLock); + } else { + dispatchMediaKeyEvent(keyEvent, needWakeLock); + } + } + + /** + * Handles the dispatching of the media button events to the telephony package. + * Precondition: mMediaReceiverForCalls != null + * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) { + Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + keyIntent.setPackage(mMediaReceiverForCalls.getPackageName()); + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); + } + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * Handles the dispatching of the media button events to one of the registered listeners, + * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system. + * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + } + Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + synchronized(mRCStack) { + if (!mRCStack.empty()) { + // send the intent that was registered by the client + try { + mRCStack.peek().mMediaIntent.send(mContext, + needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/, + keyIntent, this, mEventHandler); + } catch (CanceledException e) { + Log.e(TAG, "Error sending pending intent " + mRCStack.peek()); + e.printStackTrace(); + } + } else { + // legacy behavior when nobody registered their media button event receiver + // through AudioManager + if (needWakeLock) { + keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); + } + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, + mEventHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } + + /** + * The different actions performed in response to a voice button key event. + */ + private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1; + private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2; + private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3; + + private final Object mVoiceEventLock = new Object(); + private boolean mVoiceButtonDown; + private boolean mVoiceButtonHandled; + + /** + * Filter key events that may be used for voice-based interactions + * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported + * media buttons that can be used to trigger voice-based interactions. + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + if (DEBUG_RC) { + Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock); + } + + int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS; + int keyAction = keyEvent.getAction(); + synchronized (mVoiceEventLock) { + if (keyAction == KeyEvent.ACTION_DOWN) { + if (keyEvent.getRepeatCount() == 0) { + // initial down + mVoiceButtonDown = true; + mVoiceButtonHandled = false; + } else if (mVoiceButtonDown && !mVoiceButtonHandled + && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { + // long-press, start voice-based interactions + mVoiceButtonHandled = true; + voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT; + } + } else if (keyAction == KeyEvent.ACTION_UP) { + if (mVoiceButtonDown) { + // voice button up + mVoiceButtonDown = false; + if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { + voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS; + } + } + } + }//synchronized (mVoiceEventLock) + + // take action after media button event filtering for voice-based interactions + switch (voiceButtonAction) { + case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS: + if (DEBUG_RC) Log.v(TAG, " ignore key event"); + break; + case VOICEBUTTON_ACTION_START_VOICE_INPUT: + if (DEBUG_RC) Log.v(TAG, " start voice-based interactions"); + // then start the voice-based interactions + startVoiceBasedInteractions(needWakeLock); + break; + case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: + if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); + sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); + break; + } + } + + private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) { + // send DOWN event + KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN); + dispatchMediaKeyEvent(keyEvent, needWakeLock); + // send UP event + keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP); + dispatchMediaKeyEvent(keyEvent, needWakeLock); + + } + + private class PackageIntentsReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_PACKAGE_REMOVED) + || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) { + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // a package is being removed, not replaced + String packageName = intent.getData().getSchemeSpecificPart(); + if (packageName != null) { + cleanupMediaButtonReceiverForPackage(packageName, true); + } + } + } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) + || action.equals(Intent.ACTION_PACKAGE_CHANGED)) { + String packageName = intent.getData().getSchemeSpecificPart(); + if (packageName != null) { + cleanupMediaButtonReceiverForPackage(packageName, false); + } + } + } + } + + protected static boolean isMediaKeyCode(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + case KeyEvent.KEYCODE_MEDIA_CLOSE: + case KeyEvent.KEYCODE_MEDIA_EJECT: + case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: + return true; + default: + return false; + } + } + + private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) { + if (keyEvent == null) { + return false; + } + return MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode()); + } + + /** + * Checks whether the given key code is one that can trigger the launch of voice-based + * interactions. + * @param keyCode the key code associated with the key event + * @return true if the key is one of the supported voice-based interaction triggers + */ + private static boolean isValidVoiceInputKeyCode(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { + return true; + } else { + return false; + } + } + + /** + * Tell the system to start voice-based interactions / voice commands + */ + private void startVoiceBasedInteractions(boolean needWakeLock) { + Intent voiceIntent = null; + // select which type of search to launch: + // - screen on and device unlocked: action is ACTION_WEB_SEARCH + // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE + // with EXTRA_SECURE set to true if the device is securely locked + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); + if (!isLocked && pm.isScreenOn()) { + voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); + Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH"); + } else { + voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, + isLocked && mKeyguardManager.isKeyguardSecure()); + Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE"); + } + // start the search activity + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + } + final long identity = Binder.clearCallingIdentity(); + try { + if (voiceIntent != null) { + voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT); + } + } catch (ActivityNotFoundException e) { + Log.w(TAG, "No activity for search: " + e); + } finally { + Binder.restoreCallingIdentity(identity); + if (needWakeLock) { + mMediaEventWakeLock.release(); + } + } + } + + private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number + + // only set when wakelock was acquired, no need to check value when received + private static final String EXTRA_WAKELOCK_ACQUIRED = + "android.media.AudioService.WAKELOCK_ACQUIRED"; + + public void onSendFinished(PendingIntent pendingIntent, Intent intent, + int resultCode, String resultData, Bundle resultExtras) { + if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) { + mMediaEventWakeLock.release(); + } + } + + BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + Bundle extras = intent.getExtras(); + if (extras == null) { + return; + } + if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) { + mMediaEventWakeLock.release(); + } + } + }; + + /** + * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack + */ + private final Object mCurrentRcLock = new Object(); + /** + * The one remote control client which will receive a request for display information. + * This object may be null. + * Access protected by mCurrentRcLock. + */ + private IRemoteControlClient mCurrentRcClient = null; + /** + * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant + * if mCurrentRcClient is null + */ + private PendingIntent mCurrentRcClientIntent = null; + + private final static int RC_INFO_NONE = 0; + private final static int RC_INFO_ALL = + RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART | + RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA | + RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA | + RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE; + + /** + * A monotonically increasing generation counter for mCurrentRcClient. + * Only accessed with a lock on mCurrentRcLock. + * No value wrap-around issues as we only act on equal values. + */ + private int mCurrentRcClientGen = 0; + + /** + * Inner class to monitor remote control client deaths, and remove the client for the + * remote control stack if necessary. + */ + private class RcClientDeathHandler implements IBinder.DeathRecipient { + final private IBinder mCb; // To be notified of client's death + final private PendingIntent mMediaIntent; + + RcClientDeathHandler(IBinder cb, PendingIntent pi) { + mCb = cb; + mMediaIntent = pi; + } + + public void binderDied() { + Log.w(TAG, " RemoteControlClient died"); + // remote control client died, make sure the displays don't use it anymore + // by setting its remote control client to null + registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/); + // the dead client was maybe handling remote playback, reevaluate + postReevaluateRemote(); + } + + public IBinder getBinder() { + return mCb; + } + } + + /** + * A global counter for RemoteControlClient identifiers + */ + private static int sLastRccId = 0; + + private class RemotePlaybackState { + int mRccId; + int mVolume; + int mVolumeMax; + int mVolumeHandling; + + private RemotePlaybackState(int id, int vol, int volMax) { + mRccId = id; + mVolume = vol; + mVolumeMax = volMax; + mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + } + } + + /** + * Internal cache for the playback information of the RemoteControlClient whose volume gets to + * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack + * every time we need this info. + */ + private RemotePlaybackState mMainRemote; + /** + * Indicates whether the "main" RemoteControlClient is considered active. + * Use synchronized on mMainRemote. + */ + private boolean mMainRemoteIsActive; + /** + * Indicates whether there is remote playback going on. True even if there is no "active" + * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it + * handles remote playback. + * Use synchronized on mMainRemote. + */ + private boolean mHasRemotePlayback; + + private static class RccPlaybackState { + public int mState; + public long mPositionMs; + public float mSpeed; + + public RccPlaybackState(int state, long positionMs, float speed) { + mState = state; + mPositionMs = positionMs; + mSpeed = speed; + } + + public void reset() { + mState = RemoteControlClient.PLAYSTATE_STOPPED; + mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; + mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; + } + + @Override + public String toString() { + return stateToString() + ", " + posToString() + ", " + mSpeed + "X"; + } + + private String posToString() { + if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) { + return "PLAYBACK_POSITION_INVALID"; + } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { + return "PLAYBACK_POSITION_ALWAYS_UNKNOWN"; + } else { + return (String.valueOf(mPositionMs) + "ms"); + } + } + + private String stateToString() { + switch (mState) { + case RemoteControlClient.PLAYSTATE_NONE: + return "PLAYSTATE_NONE"; + case RemoteControlClient.PLAYSTATE_STOPPED: + return "PLAYSTATE_STOPPED"; + case RemoteControlClient.PLAYSTATE_PAUSED: + return "PLAYSTATE_PAUSED"; + case RemoteControlClient.PLAYSTATE_PLAYING: + return "PLAYSTATE_PLAYING"; + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + return "PLAYSTATE_FAST_FORWARDING"; + case RemoteControlClient.PLAYSTATE_REWINDING: + return "PLAYSTATE_REWINDING"; + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return "PLAYSTATE_SKIPPING_FORWARDS"; + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + return "PLAYSTATE_SKIPPING_BACKWARDS"; + case RemoteControlClient.PLAYSTATE_BUFFERING: + return "PLAYSTATE_BUFFERING"; + case RemoteControlClient.PLAYSTATE_ERROR: + return "PLAYSTATE_ERROR"; + default: + return "[invalid playstate]"; + } + } + } + + protected static class RemoteControlStackEntry implements DeathRecipient { + public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + final public MediaFocusControl mController; + /** + * The target for the ACTION_MEDIA_BUTTON events. + * Always non null. + */ + final public PendingIntent mMediaIntent; + /** + * The registered media button event receiver. + * Always non null. + */ + final public ComponentName mReceiverComponent; + public IBinder mToken; + public String mCallingPackageName; + public int mCallingUid; + /** + * Provides access to the information to display on the remote control. + * May be null (when a media button event receiver is registered, + * but no remote control client has been registered) */ + public IRemoteControlClient mRcClient; + public RcClientDeathHandler mRcClientDeathHandler; + /** + * Information only used for non-local playback + */ + public int mPlaybackType; + public int mPlaybackVolume; + public int mPlaybackVolumeMax; + public int mPlaybackVolumeHandling; + public int mPlaybackStream; + public RccPlaybackState mPlaybackState; + public IRemoteVolumeObserver mRemoteVolumeObs; + + public void resetPlaybackInfo() { + mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL; + mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + mPlaybackStream = AudioManager.STREAM_MUSIC; + mPlaybackState.reset(); + mRemoteVolumeObs = null; + } + + /** precondition: mediaIntent != null */ + public RemoteControlStackEntry(MediaFocusControl controller, PendingIntent mediaIntent, + ComponentName eventReceiver, IBinder token) { + mController = controller; + mMediaIntent = mediaIntent; + mReceiverComponent = eventReceiver; + mToken = token; + mCallingUid = -1; + mRcClient = null; + mRccId = ++sLastRccId; + mPlaybackState = new RccPlaybackState( + RemoteControlClient.PLAYSTATE_STOPPED, + RemoteControlClient.PLAYBACK_POSITION_INVALID, + RemoteControlClient.PLAYBACK_SPEED_1X); + + resetPlaybackInfo(); + if (mToken != null) { + try { + mToken.linkToDeath(this, 0); + } catch (RemoteException e) { + mController.mEventHandler.post(new Runnable() { + @Override public void run() { + mController.unregisterMediaButtonIntent(mMediaIntent); + } + }); + } + } + } + + public void unlinkToRcClientDeath() { + if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) { + try { + mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0); + mRcClientDeathHandler = null; + } catch (java.util.NoSuchElementException e) { + // not much we can do here + Log.e(TAG, "Encountered " + e + " in unlinkToRcClientDeath()"); + e.printStackTrace(); + } + } + } + + public void destroy() { + unlinkToRcClientDeath(); + if (mToken != null) { + mToken.unlinkToDeath(this, 0); + mToken = null; + } + } + + @Override + public void binderDied() { + mController.unregisterMediaButtonIntent(mMediaIntent); + } + + @Override + protected void finalize() throws Throwable { + destroy(); // unlink exception handled inside method + super.finalize(); + } + } + + /** + * The stack of remote control event receivers. + * Code sections and methods that modify the remote control event receiver stack are + * synchronized on mRCStack, but also BEFORE on mFocusLock as any change in either + * stack, audio focus or RC, can lead to a change in the remote control display + */ + private final Stack mRCStack = new Stack(); + + /** + * The component the telephony package can register so telephony calls have priority to + * handle media button events + */ + private ComponentName mMediaReceiverForCalls = null; + + /** + * Helper function: + * Display in the log the current entries in the remote control focus stack + */ + private void dumpRCStack(PrintWriter pw) { + pw.println("\nRemote Control stack entries (last is top of stack):"); + synchronized(mRCStack) { + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + pw.println(" pi: " + rcse.mMediaIntent + + " -- pack: " + rcse.mCallingPackageName + + " -- ercvr: " + rcse.mReceiverComponent + + " -- client: " + rcse.mRcClient + + " -- uid: " + rcse.mCallingUid + + " -- type: " + rcse.mPlaybackType + + " state: " + rcse.mPlaybackState); + } + } + } + + /** + * Helper function: + * Display in the log the current entries in the remote control stack, focusing + * on RemoteControlClient data + */ + private void dumpRCCStack(PrintWriter pw) { + pw.println("\nRemote Control Client stack entries (last is top of stack):"); + synchronized(mRCStack) { + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + pw.println(" uid: " + rcse.mCallingUid + + " -- id: " + rcse.mRccId + + " -- type: " + rcse.mPlaybackType + + " -- state: " + rcse.mPlaybackState + + " -- vol handling: " + rcse.mPlaybackVolumeHandling + + " -- vol: " + rcse.mPlaybackVolume + + " -- volMax: " + rcse.mPlaybackVolumeMax + + " -- volObs: " + rcse.mRemoteVolumeObs); + } + synchronized(mCurrentRcLock) { + pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); + } + } + synchronized (mMainRemote) { + pw.println("\nRemote Volume State:"); + pw.println(" has remote: " + mHasRemotePlayback); + pw.println(" is remote active: " + mMainRemoteIsActive); + pw.println(" rccId: " + mMainRemote.mRccId); + pw.println(" volume handling: " + + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ? + "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)")); + pw.println(" volume: " + mMainRemote.mVolume); + pw.println(" volume steps: " + mMainRemote.mVolumeMax); + } + } + + /** + * Helper function: + * Display in the log the current entries in the list of remote control displays + */ + private void dumpRCDList(PrintWriter pw) { + pw.println("\nRemote Control Display list entries:"); + synchronized(mRCStack) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + pw.println(" IRCD: " + di.mRcDisplay + + " -- w:" + di.mArtworkExpectedWidth + + " -- h:" + di.mArtworkExpectedHeight + + " -- wantsPosSync:" + di.mWantsPositionSync + + " -- " + (di.mEnabled ? "enabled" : "disabled")); + } + } + } + + /** + * Helper function: + * Remove any entry in the remote control stack that has the same package name as packageName + * Pre-condition: packageName != null + */ + private void cleanupMediaButtonReceiverForPackage(String packageName, boolean removeAll) { + synchronized(mRCStack) { + if (mRCStack.empty()) { + return; + } else { + final PackageManager pm = mContext.getPackageManager(); + RemoteControlStackEntry oldTop = mRCStack.peek(); + Iterator stackIterator = mRCStack.iterator(); + // iterate over the stack entries + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); + if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) { + // a stack entry is from the package being removed, remove it from the stack + stackIterator.remove(); + rcse.destroy(); + } else if (rcse.mReceiverComponent != null) { + try { + // Check to see if this receiver still exists. + pm.getReceiverInfo(rcse.mReceiverComponent, 0); + } catch (PackageManager.NameNotFoundException e) { + // Not found -- remove it! + stackIterator.remove(); + rcse.destroy(); + } + } + } + if (mRCStack.empty()) { + // no saved media button receiver + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, + null)); + } else if (oldTop != mRCStack.peek()) { + // the top of the stack has changed, save it in the system settings + // by posting a message to persist it; only do this however if it has + // a concrete component name (is not a transient registration) + RemoteControlStackEntry rcse = mRCStack.peek(); + if (rcse.mReceiverComponent != null) { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, + rcse.mReceiverComponent)); + } + } + } + } + } + + /** + * Helper function: + * Restore remote control receiver from the system settings. + */ + protected void restoreMediaButtonReceiver() { + String receiverName = Settings.System.getStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); + if ((null != receiverName) && !receiverName.isEmpty()) { + ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); + if (eventReceiver == null) { + // an invalid name was persisted + return; + } + // construct a PendingIntent targeted to the restored component name + // for the media button and register it + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + // the associated intent will be handled by the component being registered + mediaButtonIntent.setComponent(eventReceiver); + PendingIntent pi = PendingIntent.getBroadcast(mContext, + 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); + registerMediaButtonIntent(pi, eventReceiver, null); + } + } + + /** + * Helper function: + * Set the new remote control receiver at the top of the RC focus stack. + * Called synchronized on mAudioFocusLock, then mRCStack + * precondition: mediaIntent != null + */ + private void pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent, ComponentName target, + IBinder token) { + // already at top of stack? + if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) { + return; + } + if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(), + mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) { + return; + } + RemoteControlStackEntry rcse = null; + boolean wasInsideStack = false; + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + rcse = mRCStack.elementAt(index); + if(rcse.mMediaIntent.equals(mediaIntent)) { + // ok to remove element while traversing the stack since we're leaving the loop + mRCStack.removeElementAt(index); + wasInsideStack = true; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + if (!wasInsideStack) { + rcse = new RemoteControlStackEntry(this, mediaIntent, target, token); + } + mRCStack.push(rcse); // rcse is never null + + // post message to persist the default media button receiver + if (target != null) { + mEventHandler.sendMessage( mEventHandler.obtainMessage( + MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); + } + } + + /** + * Helper function: + * Remove the remote control receiver from the RC focus stack. + * Called synchronized on mAudioFocusLock, then mRCStack + * precondition: pi != null + */ + private void removeMediaButtonReceiver_syncAfRcs(PendingIntent pi) { + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mMediaIntent.equals(pi)) { + rcse.destroy(); + // ok to remove element while traversing the stack since we're leaving the loop + mRCStack.removeElementAt(index); + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + + /** + * Helper function: + * Called synchronized on mRCStack + */ + private boolean isCurrentRcController(PendingIntent pi) { + if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) { + return true; + } + return false; + } + + private void onHandlePersistMediaButtonReceiver(ComponentName receiver) { + Settings.System.putStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, + receiver == null ? "" : receiver.flattenToString(), + UserHandle.USER_CURRENT); + } + + //========================================================================================== + // Remote control display / client + //========================================================================================== + /** + * Update the remote control displays with the new "focused" client generation + */ + private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, + PendingIntent newMediaIntent, boolean clearing) { + synchronized(mRCStack) { + if (mRcDisplays.size() > 0) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + try { + di.mRcDisplay.setCurrentClientId( + newClientGeneration, newMediaIntent, clearing); + } catch (RemoteException e) { + Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e); + di.release(); + displayIterator.remove(); + } + } + } + } + } + + /** + * Update the remote control clients with the new "focused" client generation + */ + private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) { + // (using an iterator on the stack so we can safely remove an entry if needed, + // traversal order doesn't matter here as we update all entries) + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry se = stackIterator.next(); + if ((se != null) && (se.mRcClient != null)) { + try { + se.mRcClient.setCurrentClientGenerationId(newClientGeneration); + } catch (RemoteException e) { + Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e); + stackIterator.remove(); + se.unlinkToRcClientDeath(); + } + } + } + } + + /** + * Update the displays and clients with the new "focused" client generation and name + * @param newClientGeneration the new generation value matching a client update + * @param newMediaIntent the media button event receiver associated with the client. + * May be null, which implies there is no registered media button event receiver. + * @param clearing true if the new client generation value maps to a remote control update + * where the display should be cleared. + */ + private void setNewRcClient_syncRcsCurrc(int newClientGeneration, + PendingIntent newMediaIntent, boolean clearing) { + // send the new valid client generation ID to all displays + setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing); + // send the new valid client generation ID to all clients + setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration); + } + + /** + * Called when processing MSG_RCDISPLAY_CLEAR event + */ + private void onRcDisplayClear() { + if (DEBUG_RC) Log.i(TAG, "Clear remote control display"); + + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + mCurrentRcClientGen++; + // synchronously update the displays and clients with the new client generation + setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, + null /*newMediaIntent*/, true /*clearing*/); + } + } + } + + /** + * Called when processing MSG_RCDISPLAY_UPDATE event + */ + private void onRcDisplayUpdate(RemoteControlStackEntry rcse, int flags /* USED ?*/) { + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(rcse.mRcClient))) { + if (DEBUG_RC) Log.i(TAG, "Display/update remote control "); + + mCurrentRcClientGen++; + // synchronously update the displays and clients with + // the new client generation + setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, + rcse.mMediaIntent /*newMediaIntent*/, + false /*clearing*/); + + // tell the current client that it needs to send info + try { + //TODO change name to informationRequestForAllDisplays() + mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } else { + // the remote control display owner has changed between the + // the message to update the display was sent, and the time it + // gets to be processed (now) + } + } + } + } + + /** + * Called when processing MSG_RCDISPLAY_INIT_INFO event + * Causes the current RemoteControlClient to send its info (metadata, playstate...) to + * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE. + */ + private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) { + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if (mCurrentRcClient != null) { + if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); } + try { + // synchronously update the new RCD with the current client generation + // and matching PendingIntent + newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent, + false); + + // tell the current RCC that it needs to send info, but only to the new RCD + try { + mCurrentRcClient.informationRequestForDisplay(newRcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: ", e); + mCurrentRcClient = null; + } + } catch (RemoteException e) { + Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e); + } + } + } + } + } + + /** + * Helper function: + * Called synchronized on mRCStack + */ + private void clearRemoteControlDisplay_syncAfRcs() { + synchronized(mCurrentRcLock) { + mCurrentRcClient = null; + } + // will cause onRcDisplayClear() to be called in AudioService's handler thread + mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) ); + } + + /** + * Helper function for code readability: only to be called from + * checkUpdateRemoteControlDisplay_syncAfRcs() which checks the preconditions for + * this method. + * Preconditions: + * - called synchronized mAudioFocusLock then on mRCStack + * - mRCStack.isEmpty() is false + */ + private void updateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) { + RemoteControlStackEntry rcse = mRCStack.peek(); + int infoFlagsAboutToBeUsed = infoChangedFlags; + // this is where we enforce opt-in for information display on the remote controls + // with the new AudioManager.registerRemoteControlClient() API + if (rcse.mRcClient == null) { + //Log.w(TAG, "Can't update remote control display with null remote control client"); + clearRemoteControlDisplay_syncAfRcs(); + return; + } + synchronized(mCurrentRcLock) { + if (!rcse.mRcClient.equals(mCurrentRcClient)) { + // new RC client, assume every type of information shall be queried + infoFlagsAboutToBeUsed = RC_INFO_ALL; + } + mCurrentRcClient = rcse.mRcClient; + mCurrentRcClientIntent = rcse.mMediaIntent; + } + // will cause onRcDisplayUpdate() to be called in AudioService's handler thread + mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE, + infoFlagsAboutToBeUsed /* arg1 */, 0, rcse /* obj, != null */) ); + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock, then mRCStack + * Check whether the remote control display should be updated, triggers the update if required + * @param infoChangedFlags the flags corresponding to the remote control client information + * that has changed, if applicable (checking for the update conditions might trigger a + * clear, rather than an update event). + */ + private void checkUpdateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) { + // determine whether the remote control display should be refreshed + // if either stack is empty, there is a mismatch, so clear the RC display + if (mRCStack.isEmpty() || mFocusStack.isEmpty()) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // determine which entry in the AudioFocus stack to consider, and compare against the + // top of the stack for the media button event receivers : simply using the top of the + // stack would make the entry disappear from the RemoteControlDisplay in conditions such as + // notifications playing during music playback. + // Crawl the AudioFocus stack from the top until an entry is found with the following + // characteristics: + // - focus gain on STREAM_MUSIC stream + // - non-transient focus gain on a stream other than music + FocusRequester af = null; + try { + for (int index = mFocusStack.size()-1; index >= 0; index--) { + FocusRequester fr = mFocusStack.elementAt(index); + if ((fr.getStreamType() == AudioManager.STREAM_MUSIC) + || (fr.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN)) { + af = fr; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + Log.e(TAG, "Wrong index accessing audio focus stack when updating RCD: " + e); + af = null; + } + if (af == null) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // if the audio focus and RC owners belong to different packages, there is a mismatch, clear + if (!af.hasSamePackage(mRCStack.peek().mCallingPackageName)) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + // if the audio focus didn't originate from the same Uid as the one in which the remote + // control information will be retrieved, clear + if (!af.hasSameUid(mRCStack.peek().mCallingUid)) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // refresh conditions were verified: update the remote controls + // ok to call: synchronized mAudioFocusLock then on mRCStack, mRCStack is not empty + updateRemoteControlDisplay_syncAfRcs(infoChangedFlags); + } + + /** + * Helper function: + * Post a message to asynchronously move the media button event receiver associated with the + * given remote control client ID to the top of the remote control stack + * @param rccId + */ + private void postPromoteRcc(int rccId) { + sendMsg(mEventHandler, MSG_PROMOTE_RCC, SENDMSG_REPLACE, + rccId /*arg1*/, 0, null, 0/*delay*/); + } + + private void onPromoteRcc(int rccId) { + if (DEBUG_RC) { Log.d(TAG, "Promoting RCC " + rccId); } + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // ignore if given RCC ID is already at top of remote control stack + if (!mRCStack.isEmpty() && (mRCStack.peek().mRccId == rccId)) { + return; + } + int indexToPromote = -1; + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + indexToPromote = index; + break; + } + } + if (indexToPromote >= 0) { + if (DEBUG_RC) { Log.d(TAG, " moving RCC from index " + indexToPromote + + " to " + (mRCStack.size()-1)); } + final RemoteControlStackEntry rcse = mRCStack.remove(indexToPromote); + mRCStack.push(rcse); + // the RC stack changed, reevaluate the display + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + }//synchronized(mRCStack) + }//synchronized(mAudioFocusLock) + } + + /** + * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) + * precondition: mediaIntent != null + */ + protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, + IBinder token) { + Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); + + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + pushMediaButtonReceiver_syncAfRcs(mediaIntent, eventReceiver, token); + // new RC client, assume every type of information shall be queried + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + + /** + * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) + * precondition: mediaIntent != null, eventReceiver != null + */ + protected void unregisterMediaButtonIntent(PendingIntent mediaIntent) + { + Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); + + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + boolean topOfStackWillChange = isCurrentRcController(mediaIntent); + removeMediaButtonReceiver_syncAfRcs(mediaIntent); + if (topOfStackWillChange) { + // current RC client will change, assume every type of info needs to be queried + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + } + + /** + * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c) + * precondition: c != null + */ + protected void registerMediaButtonEventReceiverForCalls(ComponentName c) { + if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") + != PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Invalid permissions to register media button receiver for calls"); + return; + } + synchronized(mRCStack) { + mMediaReceiverForCalls = c; + } + } + + /** + * see AudioManager.unregisterMediaButtonEventReceiverForCalls() + */ + protected void unregisterMediaButtonEventReceiverForCalls() { + if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") + != PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Invalid permissions to unregister media button receiver for calls"); + return; + } + synchronized(mRCStack) { + mMediaReceiverForCalls = null; + } + } + + /** + * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) + * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient + * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient + * without modifying the RC stack, but while still causing the display to refresh (will + * become blank as a result of this) + */ + protected int registerRemoteControlClient(PendingIntent mediaIntent, + IRemoteControlClient rcClient, String callingPackageName) { + if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // store the new display information + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if(rcse.mMediaIntent.equals(mediaIntent)) { + // already had a remote control client? + if (rcse.mRcClientDeathHandler != null) { + // stop monitoring the old client's death + rcse.unlinkToRcClientDeath(); + } + // save the new remote control client + rcse.mRcClient = rcClient; + rcse.mCallingPackageName = callingPackageName; + rcse.mCallingUid = Binder.getCallingUid(); + if (rcClient == null) { + // here rcse.mRcClientDeathHandler is null; + rcse.resetPlaybackInfo(); + break; + } + rccId = rcse.mRccId; + + // there is a new (non-null) client: + // 1/ give the new client the displays (if any) + if (mRcDisplays.size() > 0) { + plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient); + } + // 2/ monitor the new client's death + IBinder b = rcse.mRcClient.asBinder(); + RcClientDeathHandler rcdh = + new RcClientDeathHandler(b, rcse.mMediaIntent); + try { + b.linkToDeath(rcdh, 0); + } catch (RemoteException e) { + // remote control client is DOA, disqualify it + Log.w(TAG, "registerRemoteControlClient() has a dead client " + b); + rcse.mRcClient = null; + } + rcse.mRcClientDeathHandler = rcdh; + break; + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + + // if the eventReceiver is at the top of the stack + // then check for potential refresh of the remote controls + if (isCurrentRcController(mediaIntent)) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + }//synchronized(mRCStack) + }//synchronized(mAudioFocusLock) + return rccId; + } + + /** + * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) + * rcClient is guaranteed non-null + */ + protected void unregisterRemoteControlClient(PendingIntent mediaIntent, + IRemoteControlClient rcClient) { + if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient); + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + boolean topRccChange = false; + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if ((rcse.mMediaIntent.equals(mediaIntent)) + && rcClient.equals(rcse.mRcClient)) { + // we found the IRemoteControlClient to unregister + // stop monitoring its death + rcse.unlinkToRcClientDeath(); + // reset the client-related fields + rcse.mRcClient = null; + rcse.mCallingPackageName = null; + topRccChange = (index == mRCStack.size()-1); + // there can only be one matching RCC in the RC stack, we're done + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + if (topRccChange) { + // no more RCC for the RCD, check for potential refresh of the remote controls + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + } + + + /** + * A class to encapsulate all the information about a remote control display. + * After instanciation, init() must always be called before the object is added in the list + * of displays. + * Before being removed from the list of displays, release() must always be called (otherwise + * it will leak death handlers). + */ + private class DisplayInfoForServer implements IBinder.DeathRecipient { + /** may never be null */ + private final IRemoteControlDisplay mRcDisplay; + private final IBinder mRcDisplayBinder; + private int mArtworkExpectedWidth = -1; + private int mArtworkExpectedHeight = -1; + private boolean mWantsPositionSync = false; + private ComponentName mClientNotifListComp; + private boolean mEnabled = true; + + public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { + if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); + mRcDisplay = rcd; + mRcDisplayBinder = rcd.asBinder(); + mArtworkExpectedWidth = w; + mArtworkExpectedHeight = h; + } + + public boolean init() { + try { + mRcDisplayBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + // remote control display is DOA, disqualify it + Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder); + return false; + } + return true; + } + + public void release() { + try { + mRcDisplayBinder.unlinkToDeath(this, 0); + } catch (java.util.NoSuchElementException e) { + // not much we can do here, the display should have been unregistered anyway + Log.e(TAG, "Error in DisplaInfoForServer.relase()", e); + } + } + + public void binderDied() { + synchronized(mRCStack) { + Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died"); + // remove the display from the list + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay == mRcDisplay) { + if (DEBUG_RC) Log.w(TAG, " RCD removed from list"); + displayIterator.remove(); + return; + } + } + } + } + } + + /** + * The remote control displays. + * Access synchronized on mRCStack + */ + private ArrayList mRcDisplays = new ArrayList(1); + + /** + * Plug each registered display into the specified client + * @param rcc, guaranteed non null + */ + private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + try { + rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, + di.mArtworkExpectedHeight); + if (di.mWantsPositionSync) { + rcc.setWantsSyncForDisplay(di.mRcDisplay, true); + } + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); + } + } + } + + private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, + boolean enabled) { + // let all the remote control clients know whether the given display is enabled + // (so the remote control stack traversal order doesn't matter). + final Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.enableRemoteControlDisplay(rcd, enabled); + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to client: ", e); + } + } + } + } + + /** + * Is the remote control display interface already registered + * @param rcd + * @return true if the IRemoteControlDisplay is already in the list of displays + */ + private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + return true; + } + } + return false; + } + + /** + * Register an IRemoteControlDisplay. + * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient + * at the top of the stack to update the new display with its information. + * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int) + * @param rcd the IRemoteControlDisplay to register. No effect if null. + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param listenerComp the component for the listener interface, may be null if it's not needed + * to verify it belongs to one of the enabled notification listeners + */ + private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, + ComponentName listenerComp) { + if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")"); + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) { + return; + } + DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h); + di.mEnabled = true; + di.mClientNotifListComp = listenerComp; + if (!di.init()) { + if (DEBUG_RC) Log.e(TAG, " error registering RCD"); + return; + } + // add RCD to list of displays + mRcDisplays.add(di); + + // let all the remote control clients know there is a new display (so the remote + // control stack traversal order doesn't matter). + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to client: ", e); + } + } + } + + // we have a new display, of which all the clients are now aware: have it be + // initialized wih the current gen ID and the current client info, do not + // reset the information for the other (existing) displays + sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, + w /*arg1*/, h /*arg2*/, + rcd /*obj*/, 0/*delay*/); + } + } + } + + /** + * Unregister an IRemoteControlDisplay. + * No effect if the IRemoteControlDisplay hasn't been successfully registered. + * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay) + * @param rcd the IRemoteControlDisplay to unregister. No effect if null. + */ + protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { + if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")"); + synchronized(mRCStack) { + if (rcd == null) { + return; + } + + boolean displayWasPluggedIn = false; + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext() && !displayWasPluggedIn) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + displayWasPluggedIn = true; + di.release(); + displayIterator.remove(); + } + } + + if (displayWasPluggedIn) { + // disconnect this remote control display from all the clients, so the remote + // control stack traversal order doesn't matter + final Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.unplugRemoteControlDisplay(rcd); + } catch (RemoteException e) { + Log.e(TAG, "Error disconnecting remote control display to client: ", e); + } + } + } + } else { + if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); + } + } + } + + /** + * Update the size of the artwork used by an IRemoteControlDisplay. + * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int) + * @param rcd the IRemoteControlDisplay with the new artwork size requirement + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + */ + protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { + synchronized(mRCStack) { + final Iterator displayIterator = mRcDisplays.iterator(); + boolean artworkSizeUpdate = false; + while (displayIterator.hasNext() && !artworkSizeUpdate) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { + di.mArtworkExpectedWidth = w; + di.mArtworkExpectedHeight = h; + artworkSizeUpdate = true; + } + } + } + if (artworkSizeUpdate) { + // RCD is currently plugged in and its artwork size has changed, notify all RCCs, + // stack traversal order doesn't matter + final Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e); + } + } + } + } + } + } + + /** + * Controls whether a remote control display needs periodic checks of the RemoteControlClient + * playback position to verify that the estimated position has not drifted from the actual + * position. By default the check is not performed. + * The IRemoteControlDisplay must have been previously registered for this to have any effect. + * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled + * or disabled. Not null. + * @param wantsSync if true, RemoteControlClient instances which expose their playback position + * to the framework will regularly compare the estimated playback position with the actual + * position, and will update the IRemoteControlDisplay implementation whenever a drift is + * detected. + */ + protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, + boolean wantsSync) { + synchronized(mRCStack) { + boolean rcdRegistered = false; + // store the information about this display + // (display stack traversal order doesn't matter). + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mWantsPositionSync = wantsSync; + rcdRegistered = true; + break; + } + } + if (!rcdRegistered) { + return; + } + // notify all current RemoteControlClients + // (stack traversal order doesn't matter as we notify all RCCs) + final Iterator stackIterator = mRCStack.iterator(); + while (stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mRcClient != null) { + try { + rcse.mRcClient.setWantsSyncForDisplay(rcd, wantsSync); + } catch (RemoteException e) { + Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e); + } + } + } + } + } + + protected void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + // ignore position change requests if invalid generation ID + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if (mCurrentRcClientGen != generationId) { + return; + } + } + } + // discard any unprocessed seek request in the message queue, and replace with latest + sendMsg(mEventHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */, + 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */); + } + + private void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId + + ", timeMs=" + timeMs + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) { + // tell the current client to seek to the requested location + try { + mCurrentRcClient.seekTo(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } + } + } + } + + protected void updateRemoteControlClientMetadata(int genId, int key, Rating value) { + sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA, SENDMSG_QUEUE, + genId /* arg1 */, key /* arg2 */, value /* obj */, 0 /* delay */); + } + + private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) { + if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId + + ", what=" + key + ",rating=" + value + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) { + try { + switch (key) { + case MediaMetadataEditor.RATING_KEY_BY_USER: + mCurrentRcClient.updateMetadata(genId, key, value); + break; + default: + Log.e(TAG, "unhandled metadata key " + key + " update for RCC " + + genId); + break; + } + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead", e); + mCurrentRcClient = null; + } + } + } + } + } + + protected void setPlaybackInfoForRcc(int rccId, int what, int value) { + sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE, + rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */); + } + + // handler for MSG_RCC_NEW_PLAYBACK_INFO + private void onNewPlaybackInfoForRcc(int rccId, int key, int value) { + if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId + + ", what=" + key + ",val=" + value + ")"); + synchronized(mRCStack) { + // iterating from top of stack as playback information changes are more likely + // on entries at the top of the remote control stack + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + switch (key) { + case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE: + rcse.mPlaybackType = value; + postReevaluateRemote(); + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME: + rcse.mPlaybackVolume = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolume = value; + mVolumeController.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX: + rcse.mPlaybackVolumeMax = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolumeMax = value; + mVolumeController.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING: + rcse.mPlaybackVolumeHandling = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolumeHandling = value; + mVolumeController.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_USES_STREAM: + rcse.mPlaybackStream = value; + break; + default: + Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); + break; + } + return; + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e); + } + } + } + + protected void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) { + sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE, + rccId /* arg1 */, state /* arg2 */, + new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */); + } + + private void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) { + if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state + + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")"); + synchronized(mRCStack) { + // iterating from top of stack as playback information changes are more likely + // on entries at the top of the remote control stack + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + rcse.mPlaybackState = newState; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemoteIsActive = isPlaystateActive(state); + postReevaluateRemote(); + } + } + // an RCC moving to a "playing" state should become the media button + // event receiver so it can be controlled, without requiring the + // app to re-register its receiver + if (isPlaystateActive(state)) { + postPromoteRcc(rccId); + } + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e); + } + } + } + + protected void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { + sendMsg(mEventHandler, MSG_RCC_NEW_VOLUME_OBS, SENDMSG_QUEUE, + rccId /* arg1 */, 0, rvo /* obj */, 0 /* delay */); + } + + // handler for MSG_RCC_NEW_VOLUME_OBS + private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { + synchronized(mRCStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + rcse.mRemoteVolumeObs = rvo; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + } + + /** + * Checks if a remote client is active on the supplied stream type. Update the remote stream + * volume state if found and playing + * @param streamType + * @return false if no remote playing is currently playing + */ + protected boolean checkUpdateRemoteStateIfActive(int streamType) { + synchronized(mRCStack) { + // iterating from top of stack as active playback is more likely on entries at the top + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) + && isPlaystateActive(rcse.mPlaybackState.mState) + && (rcse.mPlaybackStream == streamType)) { + if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType + + ", vol =" + rcse.mPlaybackVolume); + synchronized (mMainRemote) { + mMainRemote.mRccId = rcse.mRccId; + mMainRemote.mVolume = rcse.mPlaybackVolume; + mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax; + mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling; + mMainRemoteIsActive = true; + } + return true; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + } + synchronized (mMainRemote) { + mMainRemoteIsActive = false; + } + return false; + } + + /** + * Returns true if the given playback state is considered "active", i.e. it describes a state + * where playback is happening, or about to + * @param playState the playback state to evaluate + * @return true if active, false otherwise (inactive or unknown) + */ + private static boolean isPlaystateActive(int playState) { + switch (playState) { + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_BUFFERING: + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_REWINDING: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return true; + default: + return false; + } + } + + protected void adjustRemoteVolume(int streamType, int direction, int flags) { + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + boolean volFixed = false; + synchronized (mMainRemote) { + if (!mMainRemoteIsActive) { + if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client"); + return; + } + rccId = mMainRemote.mRccId; + volFixed = (mMainRemote.mVolumeHandling == + RemoteControlClient.PLAYBACK_VOLUME_FIXED); + } + // unlike "local" stream volumes, we can't compute the new volume based on the direction, + // we can only notify the remote that volume needs to be updated, and we'll get an async' + // update through setPlaybackInfoForRcc() + if (!volFixed) { + sendVolumeUpdateToRemote(rccId, direction); + } + + // fire up the UI + mVolumeController.postRemoteVolumeChanged(streamType, flags); + } + + private void sendVolumeUpdateToRemote(int rccId, int direction) { + if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); } + if (direction == 0) { + // only handling discrete events + return; + } + IRemoteVolumeObserver rvo = null; + synchronized (mRCStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + if (rcse.mRccId == rccId) { + rvo = rcse.mRemoteVolumeObs; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + if (rvo != null) { + try { + rvo.dispatchRemoteVolumeUpdate(direction, -1); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching relative volume update", e); + } + } + } + + protected int getRemoteStreamMaxVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolumeMax; + } + } + + protected int getRemoteStreamVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolume; + } + } + + protected void setRemoteStreamVolume(int vol) { + if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); } + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return; + } + rccId = mMainRemote.mRccId; + } + IRemoteVolumeObserver rvo = null; + synchronized (mRCStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + if (rcse.mRccId == rccId) { + rvo = rcse.mRemoteVolumeObs; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + if (rvo != null) { + try { + rvo.dispatchRemoteVolumeUpdate(0, vol); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching absolute volume update", e); + } + } + } + + /** + * Call to make AudioService reevaluate whether it's in a mode where remote players should + * have their volume controlled. In this implementation this is only to reset whether + * VolumePanel should display remote volumes + */ + private void postReevaluateRemote() { + sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); + } + + private void onReevaluateRemote() { + if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); } + // is there a registered RemoteControlClient that is handling remote playback + boolean hasRemotePlayback = false; + synchronized (mRCStack) { + // iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack + // traversal order doesn't matter + Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) { + hasRemotePlayback = true; + break; + } + } + } + synchronized (mMainRemote) { + if (mHasRemotePlayback != hasRemotePlayback) { + mHasRemotePlayback = hasRemotePlayback; + mVolumeController.postRemoteSliderVisibility(hasRemotePlayback); + } + } + } + +} diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 3fbaf69576fefb53ba2ad2255107ecab0fa3cd16..0f7906ef42ec5b55c91f085430378a823f736262 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -26,7 +26,7 @@ import java.util.Map; * * The format of the media data is specified as string/value pairs. * - * Keys common to all formats, all keys not marked optional are mandatory: + * Keys common to all audio/video formats, all keys not marked optional are mandatory: * * * @@ -44,7 +44,19 @@ import java.util.Map; * for encoders, readable in the output format of decoders * * + * + * + * + * *
          NameValue TypeDescription
          {@link #KEY_FRAME_RATE}Integer or Floatencoder-only
          {@link #KEY_I_FRAME_INTERVAL}Integerencoder-only
          {@link #KEY_MAX_WIDTH}Integerdecoder-only, optional, max-resolution width
          {@link #KEY_MAX_HEIGHT}Integerdecoder-only, optional, max-resolution height
          {@link #KEY_REPEAT_PREVIOUS_FRAME_AFTER}Longvideo encoder in surface-mode only
          {@link #KEY_PUSH_BLANK_BUFFERS_ON_STOP}Integer(1)video decoder rendering to a surface only
          + * Specify both {@link #KEY_MAX_WIDTH} and {@link #KEY_MAX_HEIGHT} to enable + * adaptive playback (seamless resolution change) for a video decoder that + * supports it ({@link MediaCodecInfo.CodecCapabilities#FEATURE_AdaptivePlayback}). + * The values are used as hints for the codec: they are the maximum expected + * resolution to prepare for. Depending on codec support, preparing for larger + * maximum resolution may require more memory even if that resolution is never + * reached. These fields have no effect for codecs that do not support adaptive + * playback.

          * * Audio formats have the following keys: * @@ -57,6 +69,11 @@ import java.util.Map; * *
          {@link #KEY_FLAC_COMPRESSION_LEVEL}Integerencoder-only, optional, if content is FLAC audio, specifies the desired compression level.
          * + * Subtitle formats have the following keys: + * + * + * + *
          {@link #KEY_MIME}StringThe type of the format.
          {@link #KEY_LANGUAGE}StringThe language of the content.
          */ public final class MediaFormat { private Map mMap; @@ -67,6 +84,12 @@ public final class MediaFormat { */ public static final String KEY_MIME = "mime"; + /** + * A key describing the language of the content, using either ISO 639-1 + * or 639-2/T codes. The associated value is a string. + */ + public static final String KEY_LANGUAGE = "language"; + /** * A key describing the sample rate of an audio format. * The associated value is an integer @@ -91,6 +114,20 @@ public final class MediaFormat { */ public static final String KEY_HEIGHT = "height"; + /** + * A key describing the maximum expected width of the content in a video + * decoder format, in case there are resolution changes in the video content. + * The associated value is an integer + */ + public static final String KEY_MAX_WIDTH = "max-width"; + + /** + * A key describing the maximum expected height of the content in a video + * decoder format, in case there are resolution changes in the video content. + * The associated value is an integer + */ + public static final String KEY_MAX_HEIGHT = "max-height"; + /** A key describing the maximum size in bytes of a buffer of data * described by this MediaFormat. * The associated value is an integer @@ -131,6 +168,24 @@ public final class MediaFormat { */ public static final String KEY_SLICE_HEIGHT = "slice-height"; + /** + * Applies only when configuring a video encoder in "surface-input" mode. + * The associated value is a long and gives the time in microseconds + * after which the frame previously submitted to the encoder will be + * repeated (once) if no new frame became available since. + */ + public static final String KEY_REPEAT_PREVIOUS_FRAME_AFTER + = "repeat-previous-frame-after"; + + /** + * If specified when configuring a video decoder rendering to a surface, + * causes the decoder to output "blank", i.e. black frames to the surface + * when stopped to clear out any previously displayed contents. + * The associated value is an integer of value 1. + */ + public static final String KEY_PUSH_BLANK_BUFFERS_ON_STOP + = "push-blank-buffers-on-shutdown"; + /** * A key describing the duration (in microseconds) of the content. * The associated value is a long. @@ -166,6 +221,38 @@ public final class MediaFormat { */ public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; + /** + * A key for boolean AUTOSELECT behavior for the track. Tracks with AUTOSELECT=true + * are considered when automatically selecting a track without specific user + * choice, based on the current locale. + * This is currently only used for subtitle tracks, when the user selected + * 'Default' for the captioning locale. + * The associated value is an integer, where non-0 means TRUE. This is an optional + * field; if not specified, AUTOSELECT defaults to TRUE. + */ + public static final String KEY_IS_AUTOSELECT = "is-autoselect"; + + /** + * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is + * selected in the absence of a specific user choice. + * This is currently only used for subtitle tracks, when the user selected + * 'Default' for the captioning locale. + * The associated value is an integer, where non-0 means TRUE. This is an optional + * field; if not specified, DEFAULT is considered to be FALSE. + */ + public static final String KEY_IS_DEFAULT = "is-default"; + + + /** + * A key for the FORCED field for subtitle tracks. True if it is a + * forced subtitle track. Forced subtitle tracks are essential for the + * content and are shown even when the user turns off Captions. They + * are used for example to translate foreign/alien dialogs or signs. + * The associated value is an integer, where non-0 means TRUE. This is an + * optional field; if not specified, FORCED defaults to FALSE. + */ + public static final String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; + /* package private */ MediaFormat(Map map) { mMap = map; } @@ -195,6 +282,20 @@ public final class MediaFormat { return ((Integer)mMap.get(name)).intValue(); } + /** + * Returns the value of an integer key, or the default value if the + * key is missing or is for another type value. + * @hide + */ + public final int getInteger(String name, int defaultValue) { + try { + return getInteger(name); + } + catch (NullPointerException e) { /* no such field */ } + catch (ClassCastException e) { /* field of different type */ } + return defaultValue; + } + /** * Returns the value of a long key. */ @@ -276,6 +377,24 @@ public final class MediaFormat { return format; } + /** + * Creates a minimal subtitle format. + * @param mime The mime type of the content. + * @param language The language of the content, using either ISO 639-1 or 639-2/T + * codes. Specify null or "und" if language information is only included + * in the content. (This will also work if there are multiple language + * tracks in the content.) + */ + public static final MediaFormat createSubtitleFormat( + String mime, + String language) { + MediaFormat format = new MediaFormat(); + format.setString(KEY_MIME, mime); + format.setString(KEY_LANGUAGE, language); + + return format; + } + /** * Creates a minimal video format. * @param mime The mime type of the content. diff --git a/media/java/android/media/MediaMetadataEditor.java b/media/java/android/media/MediaMetadataEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..373ba11a7c3e910e34c0af33b0bd7f3ff90c139e --- /dev/null +++ b/media/java/android/media/MediaMetadataEditor.java @@ -0,0 +1,462 @@ +/* + * 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.media; + +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.SparseIntArray; + +/** + * An abstract class for editing and storing metadata that can be published by + * {@link RemoteControlClient}. See the {@link RemoteControlClient#editMetadata(boolean)} + * method to instantiate a {@link RemoteControlClient.MetadataEditor} object. + */ +public abstract class MediaMetadataEditor { + + private final static String TAG = "MediaMetadataEditor"; + /** + * @hide + */ + protected MediaMetadataEditor() { + } + + // Public keys for metadata used by RemoteControlClient and RemoteController. + // Note that these keys are defined here, and not in MediaMetadataRetriever + // because they are not supported by the MediaMetadataRetriever features. + /** + * The metadata key for the content artwork / album art. + */ + public final static int BITMAP_KEY_ARTWORK = + RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK; + + /** + * The metadata key for the content's average rating, not the user's rating. + * The value associated with this key is a {@link Rating} instance. + * @see #RATING_KEY_BY_USER + */ + public final static int RATING_KEY_BY_OTHERS = 101; + + /** + * The metadata key for the content's user rating. + * The value associated with this key is a {@link Rating} instance. + * This key can be flagged as "editable" (with {@link #addEditableKey(int)}) to enable + * receiving user rating values through the + * {@link android.media.RemoteControlClient.OnMetadataUpdateListener} interface. + */ + public final static int RATING_KEY_BY_USER = 0x10000001; + + /** + * @hide + * Editable key mask + */ + public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF; + + + /** + * Applies all of the metadata changes that have been set since the MediaMetadataEditor instance + * was created or since {@link #clear()} was called. + */ + public abstract void apply(); + + + /** + * @hide + * Mask of editable keys. + */ + protected long mEditableKeys; + + /** + * @hide + */ + protected boolean mMetadataChanged = false; + + /** + * @hide + */ + protected boolean mApplied = false; + + /** + * @hide + */ + protected boolean mArtworkChanged = false; + + /** + * @hide + */ + protected Bitmap mEditorArtwork; + + /** + * @hide + */ + protected Bundle mEditorMetadata; + + + /** + * Clears all the pending metadata changes set since the MediaMetadataEditor instance was + * created or since this method was last called. + * Note that clearing the metadata doesn't reset the editable keys + * (use {@link #removeEditableKeys()} instead). + */ + public synchronized void clear() { + if (mApplied) { + Log.e(TAG, "Can't clear a previously applied MediaMetadataEditor"); + return; + } + mEditorMetadata.clear(); + mEditorArtwork = null; + } + + /** + * Flags the given key as being editable. + * This should only be used by metadata publishers, such as {@link RemoteControlClient}, + * which will declare the metadata field as eligible to be updated, with new values + * received through the {@link RemoteControlClient.OnMetadataUpdateListener} interface. + * @param key the type of metadata that can be edited. The supported key is + * {@link #RATING_KEY_BY_USER}. + */ + public synchronized void addEditableKey(int key) { + if (mApplied) { + Log.e(TAG, "Can't change editable keys of a previously applied MetadataEditor"); + return; + } + // only one editable key at the moment, so we're not wasting memory on an array + // of editable keys to check the validity of the key, just hardcode the supported key. + if (key == RATING_KEY_BY_USER) { + mEditableKeys |= (KEY_EDITABLE_MASK & key); + mMetadataChanged = true; + } else { + Log.e(TAG, "Metadata key " + key + " cannot be edited"); + } + } + + /** + * Causes all metadata fields to be read-only. + */ + public synchronized void removeEditableKeys() { + if (mApplied) { + Log.e(TAG, "Can't remove all editable keys of a previously applied MetadataEditor"); + return; + } + if (mEditableKeys != 0) { + mEditableKeys = 0; + mMetadataChanged = true; + } + } + + /** + * Retrieves the keys flagged as editable. + * @return null if there are no editable keys, or an array containing the keys. + */ + public synchronized int[] getEditableKeys() { + // only one editable key supported here + if (mEditableKeys == RATING_KEY_BY_USER) { + int[] keys = { RATING_KEY_BY_USER }; + return keys; + } else { + return null; + } + } + + /** + * Adds textual information. + * Note that none of the information added after {@link #apply()} has been called, + * will be available to consumers of metadata stored by the MediaMetadataEditor. + * @param key The identifier of a the metadata field to set. Valid values are + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. + * @param value The text for the given key, or {@code null} to signify there is no valid + * information for the field. + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + */ + public synchronized MediaMetadataEditor putString(int key, String value) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) { + throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); + } + mEditorMetadata.putString(String.valueOf(key), value); + mMetadataChanged = true; + return this; + } + + /** + * Adds numerical information. + * Note that none of the information added after {@link #apply()} has been called + * will be available to consumers of metadata stored by the MediaMetadataEditor. + * @param key the identifier of a the metadata field to set. Valid values are + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value + * expressed in milliseconds), + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. + * @param value The long value for the given key + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + * @throws IllegalArgumentException + */ + public synchronized MediaMetadataEditor putLong(int key, long value) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) { + throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); + } + mEditorMetadata.putLong(String.valueOf(key), value); + mMetadataChanged = true; + return this; + } + + /** + * Adds image. + * @param key the identifier of the bitmap to set. The only valid value is + * {@link #BITMAP_KEY_ARTWORK} + * @param bitmap The bitmap for the artwork, or null if there isn't any. + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + * @throws IllegalArgumentException + * @see android.graphics.Bitmap + */ + public synchronized MediaMetadataEditor putBitmap(int key, Bitmap bitmap) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + if (key != BITMAP_KEY_ARTWORK) { + throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); + } + mEditorArtwork = bitmap; + mArtworkChanged = true; + return this; + } + + /** + * Adds information stored as an instance. + * Note that none of the information added after {@link #apply()} has been called + * will be available to consumers of metadata stored by the MediaMetadataEditor. + * @param key the identifier of a the metadata field to set. Valid keys for a: + *
            + *
          • {@link Bitmap} object are {@link #BITMAP_KEY_ARTWORK},
          • + *
          • {@link String} object are the same as for {@link #putString(int, String)}
          • + *
          • {@link Long} object are the same as for {@link #putLong(int, long)}
          • + *
          • {@link Rating} object are {@link #RATING_KEY_BY_OTHERS} + * and {@link #RATING_KEY_BY_USER}.
          • + *
          + * @param value the metadata to add. + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + * @throws IllegalArgumentException + */ + public synchronized MediaMetadataEditor putObject(int key, Object value) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + switch(METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) { + case METADATA_TYPE_LONG: + if (value instanceof Long) { + return putLong(key, ((Long)value).longValue()); + } else { + throw(new IllegalArgumentException("Not a non-null Long for key "+ key)); + } + case METADATA_TYPE_STRING: + if ((value == null) || (value instanceof String)) { + return putString(key, (String) value); + } else { + throw(new IllegalArgumentException("Not a String for key "+ key)); + } + case METADATA_TYPE_RATING: + mEditorMetadata.putParcelable(String.valueOf(key), (Parcelable)value); + mMetadataChanged = true; + break; + case METADATA_TYPE_BITMAP: + if ((value == null) || (value instanceof Bitmap)) { + return putBitmap(key, (Bitmap) value); + } else { + throw(new IllegalArgumentException("Not a Bitmap for key "+ key)); + } + default: + throw(new IllegalArgumentException("Invalid key "+ key)); + } + return this; + } + + + /** + * Returns the long value for the key. + * @param key one of the keys supported in {@link #putLong(int, long)} + * @param defaultValue the value returned if the key is not present + * @return the long value for the key, or the supplied default value if the key is not present + * @throws IllegalArgumentException + */ + public synchronized long getLong(int key, long defaultValue) + throws IllegalArgumentException { + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) { + throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); + } + return mEditorMetadata.getLong(String.valueOf(key), defaultValue); + } + + /** + * Returns the {@link String} value for the key. + * @param key one of the keys supported in {@link #putString(int, String)} + * @param defaultValue the value returned if the key is not present + * @return the {@link String} value for the key, or the supplied default value if the key is + * not present + * @throws IllegalArgumentException + */ + public synchronized String getString(int key, String defaultValue) + throws IllegalArgumentException { + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) { + throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); + } + return mEditorMetadata.getString(String.valueOf(key), defaultValue); + } + + /** + * Returns the {@link Bitmap} value for the key. + * @param key the {@link #BITMAP_KEY_ARTWORK} key + * @param defaultValue the value returned if the key is not present + * @return the {@link Bitmap} value for the key, or the supplied default value if the key is + * not present + * @throws IllegalArgumentException + */ + public synchronized Bitmap getBitmap(int key, Bitmap defaultValue) + throws IllegalArgumentException { + if (key != BITMAP_KEY_ARTWORK) { + throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); + } + return (mEditorArtwork != null ? mEditorArtwork : defaultValue); + } + + /** + * Returns an object representation of the value for the key + * @param key one of the keys supported in {@link #putObject(int, Object)} + * @param defaultValue the value returned if the key is not present + * @return the object for the key, as a {@link Long}, {@link Bitmap}, {@link String}, or + * {@link Rating} depending on the key value, or the supplied default value if the key is + * not present + * @throws IllegalArgumentException + */ + public synchronized Object getObject(int key, Object defaultValue) + throws IllegalArgumentException { + switch (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) { + case METADATA_TYPE_LONG: + if (mEditorMetadata.containsKey(String.valueOf(key))) { + return mEditorMetadata.getLong(String.valueOf(key)); + } else { + return defaultValue; + } + case METADATA_TYPE_STRING: + if (mEditorMetadata.containsKey(String.valueOf(key))) { + return mEditorMetadata.getString(String.valueOf(key)); + } else { + return defaultValue; + } + case METADATA_TYPE_RATING: + if (mEditorMetadata.containsKey(String.valueOf(key))) { + return mEditorMetadata.getParcelable(String.valueOf(key)); + } else { + return defaultValue; + } + case METADATA_TYPE_BITMAP: + // only one key for Bitmap supported, value is not stored in mEditorMetadata Bundle + if (key == BITMAP_KEY_ARTWORK) { + return (mEditorArtwork != null ? mEditorArtwork : defaultValue); + } // else: fall through to invalid key handling + default: + throw(new IllegalArgumentException("Invalid key "+ key)); + } + } + + + /** + * @hide + */ + protected static final int METADATA_TYPE_INVALID = -1; + /** + * @hide + */ + protected static final int METADATA_TYPE_LONG = 0; + + /** + * @hide + */ + protected static final int METADATA_TYPE_STRING = 1; + + /** + * @hide + */ + protected static final int METADATA_TYPE_BITMAP = 2; + + /** + * @hide + */ + protected static final int METADATA_TYPE_RATING = 3; + + /** + * @hide + */ + protected static final SparseIntArray METADATA_KEYS_TYPE; + + static { + METADATA_KEYS_TYPE = new SparseIntArray(17); + // NOTE: if adding to the list below, make sure you increment the array initialization size + // keys with long values + METADATA_KEYS_TYPE.put( + MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, METADATA_TYPE_LONG); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_TYPE_LONG); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_TYPE_LONG); + // keys with String values + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put( + MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put( + MediaMetadataRetriever.METADATA_KEY_COMPILATION, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_TYPE_STRING); + // keys with Bitmap values + METADATA_KEYS_TYPE.put(BITMAP_KEY_ARTWORK, METADATA_TYPE_BITMAP); + // keys with Rating values + METADATA_KEYS_TYPE.put(RATING_KEY_BY_OTHERS, METADATA_TYPE_RATING); + METADATA_KEYS_TYPE.put(RATING_KEY_BY_USER, METADATA_TYPE_RATING); + } +} diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 774964e4b079f54c8fc69a07b7f20e2d637beb66..65a9308b70f5251721174546b6427fa7a2ec244f 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -92,6 +92,7 @@ final public class MediaMuxer { Object[] values); private static native void nativeSetOrientationHint(int nativeObject, int degrees); + private static native void nativeSetLocation(int nativeObject, int latitude, int longitude); private static native void nativeWriteSampleData(int nativeObject, int trackIndex, ByteBuffer byteBuf, int offset, int size, long presentationTimeUs, int flags); @@ -164,6 +165,41 @@ final public class MediaMuxer { } } + /** + * Set and store the geodata (latitude and longitude) in the output file. + * This method should be called before {@link #start}. The geodata is stored + * in udta box if the output format is + * {@link OutputFormat#MUXER_OUTPUT_MPEG_4}, and is ignored for other output + * formats. The geodata is stored according to ISO-6709 standard. + * + * @param latitude Latitude in degrees. Its value must be in the range [-90, + * 90]. + * @param longitude Longitude in degrees. Its value must be in the range + * [-180, 180]. + * @throws IllegalArgumentException If the given latitude or longitude is out + * of range. + * @throws IllegalStateException If this method is called after {@link #start}. + */ + public void setLocation(float latitude, float longitude) { + int latitudex10000 = (int) (latitude * 10000 + 0.5); + int longitudex10000 = (int) (longitude * 10000 + 0.5); + + if (latitudex10000 > 900000 || latitudex10000 < -900000) { + String msg = "Latitude: " + latitude + " out of range."; + throw new IllegalArgumentException(msg); + } + if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { + String msg = "Longitude: " + longitude + " out of range"; + throw new IllegalArgumentException(msg); + } + + if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) { + nativeSetLocation(mNativeObject, latitudex10000, longitudex10000); + } else { + throw new IllegalStateException("Can't set location due to wrong state."); + } + } + /** * Starts the muxer. *

          Make sure this is called after {@link #addTrack} and before diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index b729640a7f65d804e6a9029a664dcecbff34df5e..0abd5f861d43d181eb9f78f52f82e2e828b775b3 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -26,11 +26,13 @@ import android.net.Proxy; import android.net.ProxyProperties; import android.net.Uri; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.PowerManager; import android.util.Log; import android.view.Surface; @@ -38,14 +40,23 @@ import android.view.SurfaceHolder; import android.graphics.Bitmap; import android.graphics.SurfaceTexture; import android.media.AudioManager; +import android.media.MediaFormat; +import android.media.MediaTimeProvider; +import android.media.MediaTimeProvider.OnMediaTimeListener; +import android.media.SubtitleController; +import android.media.SubtitleData; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.lang.Runnable; import java.net.InetSocketAddress; import java.util.Map; +import java.util.Scanner; import java.util.Set; +import java.util.Vector; import java.lang.ref.WeakReference; /** @@ -515,7 +526,7 @@ import java.lang.ref.WeakReference; * thread by default has a Looper running). * */ -public class MediaPlayer +public class MediaPlayer implements SubtitleController.Listener { /** Constant to retrieve only the new metadata since the last @@ -588,6 +599,11 @@ public class MediaPlayer mEventHandler = null; } + mTimeProvider = new TimeProvider(this); + mOutOfBandSubtitleTracks = new Vector(); + mOpenSubtitleSources = new Vector(); + mInbandSubtitleTracks = new SubtitleTrack[0]; + /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ @@ -639,7 +655,6 @@ public class MediaPlayer * * @param reply Output parcel with the data returned by the * native player. - * * {@hide} */ public void invoke(Parcel request, Parcel reply) { @@ -1336,6 +1351,11 @@ public class MediaPlayer mOnInfoListener = null; mOnVideoSizeChangedListener = null; mOnTimedTextListener = null; + if (mTimeProvider != null) { + mTimeProvider.close(); + mTimeProvider = null; + } + mOnSubtitleDataListener = null; _release(); } @@ -1347,10 +1367,32 @@ public class MediaPlayer * data source and calling prepare(). */ public void reset() { + mSelectedSubtitleTrackIndex = -1; + synchronized(mOpenSubtitleSources) { + for (final InputStream is: mOpenSubtitleSources) { + try { + is.close(); + } catch (IOException e) { + } + } + mOpenSubtitleSources.clear(); + } + mOutOfBandSubtitleTracks.clear(); + mInbandSubtitleTracks = new SubtitleTrack[0]; + if (mSubtitleController != null) { + mSubtitleController.reset(); + } + if (mTimeProvider != null) { + mTimeProvider.close(); + mTimeProvider = null; + } + stayAwake(false); _reset(); // make sure none of the listeners get called anymore - mEventHandler.removeCallbacksAndMessages(null); + if (mEventHandler != null) { + mEventHandler.removeCallbacksAndMessages(null); + } disableProxyListener(); } @@ -1409,13 +1451,6 @@ public class MediaPlayer setVolume(volume, volume); } - /** - * Currently not implemented, returns null. - * @deprecated - * @hide - */ - public native Bitmap getFrameAt(int msec) throws IllegalStateException; - /** * Sets the audio session ID. * @@ -1458,100 +1493,6 @@ public class MediaPlayer */ public native void attachAuxEffect(int effectId); - /* Do not change these values (starting with KEY_PARAMETER) without updating - * their counterparts in include/media/mediaplayer.h! - */ - - // There are currently no defined keys usable from Java with get*Parameter. - // But if any keys are defined, the order must be kept in sync with include/media/mediaplayer.h. - // private static final int KEY_PARAMETER_... = ...; - - /** - * Sets the parameter indicated by key. - * @param key key indicates the parameter to be set. - * @param value value of the parameter to be set. - * @return true if the parameter is set successfully, false otherwise - * {@hide} - */ - public native boolean setParameter(int key, Parcel value); - - /** - * Sets the parameter indicated by key. - * @param key key indicates the parameter to be set. - * @param value value of the parameter to be set. - * @return true if the parameter is set successfully, false otherwise - * {@hide} - */ - public boolean setParameter(int key, String value) { - Parcel p = Parcel.obtain(); - p.writeString(value); - boolean ret = setParameter(key, p); - p.recycle(); - return ret; - } - - /** - * Sets the parameter indicated by key. - * @param key key indicates the parameter to be set. - * @param value value of the parameter to be set. - * @return true if the parameter is set successfully, false otherwise - * {@hide} - */ - public boolean setParameter(int key, int value) { - Parcel p = Parcel.obtain(); - p.writeInt(value); - boolean ret = setParameter(key, p); - p.recycle(); - return ret; - } - - /* - * Gets the value of the parameter indicated by key. - * @param key key indicates the parameter to get. - * @param reply value of the parameter to get. - */ - private native void getParameter(int key, Parcel reply); - - /** - * Gets the value of the parameter indicated by key. - * The caller is responsible for recycling the returned parcel. - * @param key key indicates the parameter to get. - * @return value of the parameter. - * {@hide} - */ - public Parcel getParcelParameter(int key) { - Parcel p = Parcel.obtain(); - getParameter(key, p); - return p; - } - - /** - * Gets the value of the parameter indicated by key. - * @param key key indicates the parameter to get. - * @return value of the parameter. - * {@hide} - */ - public String getStringParameter(int key) { - Parcel p = Parcel.obtain(); - getParameter(key, p); - String ret = p.readString(); - p.recycle(); - return ret; - } - - /** - * Gets the value of the parameter indicated by key. - * @param key key indicates the parameter to get. - * @return value of the parameter. - * {@hide} - */ - public int getIntParameter(int key) { - Parcel p = Parcel.obtain(); - getParameter(key, p); - int ret = p.readInt(); - p.recycle(); - return ret; - } /** * Sets the send level of the player to the attached auxiliary effect @@ -1628,20 +1569,56 @@ public class MediaPlayer * ISO-639-2 language code, "und", is returned. */ public String getLanguage() { - return mLanguage; + String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); + return language == null ? "und" : language; + } + + /** + * Gets the {@link MediaFormat} of the track. If the format is + * unknown or could not be determined, null is returned. + */ + public MediaFormat getFormat() { + if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT + || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + return mFormat; + } + return null; } public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; public static final int MEDIA_TRACK_TYPE_VIDEO = 1; public static final int MEDIA_TRACK_TYPE_AUDIO = 2; public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; + /** @hide */ + public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; final int mTrackType; - final String mLanguage; + final MediaFormat mFormat; TrackInfo(Parcel in) { mTrackType = in.readInt(); - mLanguage = in.readString(); + // TODO: parcel in the full MediaFormat + String language = in.readString(); + + if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT) { + mFormat = MediaFormat.createSubtitleFormat( + MEDIA_MIMETYPE_TEXT_SUBRIP, language); + } else if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + mFormat = MediaFormat.createSubtitleFormat( + MEDIA_MIMETYPE_TEXT_VTT, language); + mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); + mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); + mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); + } else { + mFormat = new MediaFormat(); + mFormat.setString(MediaFormat.KEY_LANGUAGE, language); + } + } + + /** @hide */ + TrackInfo(int type, MediaFormat format) { + mTrackType = type; + mFormat = format; } /** @@ -1658,7 +1635,13 @@ public class MediaPlayer @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mTrackType); - dest.writeString(mLanguage); + dest.writeString(getLanguage()); + + if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); + } } /** @@ -1688,6 +1671,19 @@ public class MediaPlayer * @throws IllegalStateException if it is called in an invalid state. */ public TrackInfo[] getTrackInfo() throws IllegalStateException { + TrackInfo trackInfo[] = getInbandTrackInfo(); + // add out-of-band tracks + TrackInfo allTrackInfo[] = new TrackInfo[trackInfo.length + mOutOfBandSubtitleTracks.size()]; + System.arraycopy(trackInfo, 0, allTrackInfo, 0, trackInfo.length); + int i = trackInfo.length; + for (SubtitleTrack track: mOutOfBandSubtitleTracks) { + allTrackInfo[i] = new TrackInfo(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, track.getFormat()); + ++i; + } + return allTrackInfo; + } + + private TrackInfo[] getInbandTrackInfo() throws IllegalStateException { Parcel request = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -1710,6 +1706,12 @@ public class MediaPlayer */ public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; + /** + * MIME type for WebVTT subtitle data. + * @hide + */ + public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt"; + /* * A helper function to check if the mime type is supported by media framework. */ @@ -1720,6 +1722,149 @@ public class MediaPlayer return false; } + private SubtitleController mSubtitleController; + + /** @hide */ + public void setSubtitleAnchor( + SubtitleController controller, + SubtitleController.Anchor anchor) { + // TODO: create SubtitleController in MediaPlayer + mSubtitleController = controller; + mSubtitleController.setAnchor(anchor); + } + + private SubtitleTrack[] mInbandSubtitleTracks; + private int mSelectedSubtitleTrackIndex = -1; + private Vector mOutOfBandSubtitleTracks; + private Vector mOpenSubtitleSources; + + private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() { + @Override + public void onSubtitleData(MediaPlayer mp, SubtitleData data) { + int index = data.getTrackIndex(); + if (index >= mInbandSubtitleTracks.length) { + return; + } + SubtitleTrack track = mInbandSubtitleTracks[index]; + if (track != null) { + try { + long runID = data.getStartTimeUs() + 1; + // TODO: move conversion into track + track.onData(new String(data.getData(), "UTF-8"), true /* eos */, runID); + track.setRunDiscardTimeMs( + runID, + (data.getStartTimeUs() + data.getDurationUs()) / 1000); + } catch (java.io.UnsupportedEncodingException e) { + Log.w(TAG, "subtitle data for track " + index + " is not UTF-8 encoded: " + e); + } + } + } + }; + + /** @hide */ + @Override + public void onSubtitleTrackSelected(SubtitleTrack track) { + if (mSelectedSubtitleTrackIndex >= 0) { + try { + selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false); + } catch (IllegalStateException e) { + } + mSelectedSubtitleTrackIndex = -1; + } + setOnSubtitleDataListener(null); + if (track == null) { + return; + } + for (int i = 0; i < mInbandSubtitleTracks.length; i++) { + if (mInbandSubtitleTracks[i] == track) { + Log.v(TAG, "Selecting subtitle track " + i); + mSelectedSubtitleTrackIndex = i; + try { + selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true); + } catch (IllegalStateException e) { + } + setOnSubtitleDataListener(mSubtitleDataListener); + break; + } + } + // no need to select out-of-band tracks + } + + /** @hide */ + public void addSubtitleSource(InputStream is, MediaFormat format) + throws IllegalStateException + { + final InputStream fIs = is; + final MediaFormat fFormat = format; + + // Ensure all input streams are closed. It is also a handy + // way to implement timeouts in the future. + synchronized(mOpenSubtitleSources) { + mOpenSubtitleSources.add(is); + } + + // process each subtitle in its own thread + final HandlerThread thread = new HandlerThread("SubtitleReadThread", + Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); + thread.start(); + Handler handler = new Handler(thread.getLooper()); + handler.post(new Runnable() { + private int addTrack() { + if (fIs == null || mSubtitleController == null) { + return MEDIA_INFO_UNSUPPORTED_SUBTITLE; + } + + SubtitleTrack track = mSubtitleController.addTrack(fFormat); + if (track == null) { + return MEDIA_INFO_UNSUPPORTED_SUBTITLE; + } + + // TODO: do the conversion in the subtitle track + Scanner scanner = new Scanner(fIs, "UTF-8"); + String contents = scanner.useDelimiter("\\A").next(); + synchronized(mOpenSubtitleSources) { + mOpenSubtitleSources.remove(fIs); + } + scanner.close(); + mOutOfBandSubtitleTracks.add(track); + track.onData(contents, true /* eos */, ~0 /* runID: keep forever */); + return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; + } + + public void run() { + int res = addTrack(); + if (mEventHandler != null) { + Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); + mEventHandler.sendMessage(m); + } + thread.getLooper().quitSafely(); + } + }); + } + + private void scanInternalSubtitleTracks() { + if (mSubtitleController == null) { + Log.e(TAG, "Should have subtitle controller already set"); + return; + } + + TrackInfo[] tracks = getInbandTrackInfo(); + SubtitleTrack[] inbandTracks = new SubtitleTrack[tracks.length]; + for (int i=0; i < tracks.length; i++) { + if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { + if (i < mInbandSubtitleTracks.length) { + inbandTracks[i] = mInbandSubtitleTracks[i]; + } else { + SubtitleTrack track = mSubtitleController.addTrack( + tracks[i].getFormat()); + inbandTracks[i] = track; + } + } + } + mInbandSubtitleTracks = inbandTracks; + mSubtitleController.selectDefaultTrack(); + } + /* TODO: Limit the total number of external timed text source to a reasonable number. */ /** @@ -1910,6 +2055,30 @@ public class MediaPlayer private void selectOrDeselectTrack(int index, boolean select) throws IllegalStateException { + // handle subtitle track through subtitle controller + SubtitleTrack track = null; + if (index < mInbandSubtitleTracks.length) { + track = mInbandSubtitleTracks[index]; + } else if (index < mInbandSubtitleTracks.length + mOutOfBandSubtitleTracks.size()) { + track = mOutOfBandSubtitleTracks.get(index - mInbandSubtitleTracks.length); + } + + if (mSubtitleController != null && track != null) { + if (select) { + mSubtitleController.selectTrack(track); + } else if (mSubtitleController.getSelectedTrack() == track) { + mSubtitleController.selectTrack(null); + } else { + Log.w(TAG, "trying to deselect track that was not selected"); + } + return; + } + + selectOrDeselectInbandTrack(index, select); + } + + private void selectOrDeselectInbandTrack(int index, boolean select) + throws IllegalStateException { Parcel request = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -1990,9 +2159,24 @@ public class MediaPlayer private static final int MEDIA_BUFFERING_UPDATE = 3; private static final int MEDIA_SEEK_COMPLETE = 4; private static final int MEDIA_SET_VIDEO_SIZE = 5; + private static final int MEDIA_STARTED = 6; + private static final int MEDIA_PAUSED = 7; + private static final int MEDIA_STOPPED = 8; + private static final int MEDIA_SKIPPED = 9; private static final int MEDIA_TIMED_TEXT = 99; private static final int MEDIA_ERROR = 100; private static final int MEDIA_INFO = 200; + private static final int MEDIA_SUBTITLE_DATA = 201; + + private TimeProvider mTimeProvider; + + /** @hide */ + public MediaTimeProvider getMediaTimeProvider() { + if (mTimeProvider == null) { + mTimeProvider = new TimeProvider(this); + } + return mTimeProvider; + } private class EventHandler extends Handler { @@ -2011,6 +2195,7 @@ public class MediaPlayer } switch(msg.what) { case MEDIA_PREPARED: + scanInternalSubtitleTracks(); if (mOnPreparedListener != null) mOnPreparedListener.onPrepared(mMediaPlayer); return; @@ -2021,14 +2206,34 @@ public class MediaPlayer stayAwake(false); return; + case MEDIA_STOPPED: + if (mTimeProvider != null) { + mTimeProvider.onStopped(); + } + break; + + case MEDIA_STARTED: + case MEDIA_PAUSED: + if (mTimeProvider != null) { + mTimeProvider.onPaused(msg.what == MEDIA_PAUSED); + } + break; + case MEDIA_BUFFERING_UPDATE: if (mOnBufferingUpdateListener != null) mOnBufferingUpdateListener.onBufferingUpdate(mMediaPlayer, msg.arg1); return; case MEDIA_SEEK_COMPLETE: - if (mOnSeekCompleteListener != null) + if (mOnSeekCompleteListener != null) { mOnSeekCompleteListener.onSeekComplete(mMediaPlayer); + } + // fall through + + case MEDIA_SKIPPED: + if (mTimeProvider != null) { + mTimeProvider.onSeekComplete(mMediaPlayer); + } return; case MEDIA_SET_VIDEO_SIZE: @@ -2049,9 +2254,21 @@ public class MediaPlayer return; case MEDIA_INFO: - if (msg.arg1 != MEDIA_INFO_VIDEO_TRACK_LAGGING) { + switch (msg.arg1) { + case MEDIA_INFO_VIDEO_TRACK_LAGGING: Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); + break; + case MEDIA_INFO_METADATA_UPDATE: + scanInternalSubtitleTracks(); + // fall through + + case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: + msg.arg1 = MEDIA_INFO_METADATA_UPDATE; + // update default track selection + mSubtitleController.selectDefaultTrack(); + break; } + if (mOnInfoListener != null) { mOnInfoListener.onInfo(mMediaPlayer, msg.arg1, msg.arg2); } @@ -2072,6 +2289,18 @@ public class MediaPlayer } return; + case MEDIA_SUBTITLE_DATA: + if (mOnSubtitleDataListener == null) { + return; + } + if (msg.obj instanceof Parcel) { + Parcel parcel = (Parcel) msg.obj; + SubtitleData data = new SubtitleData(parcel); + parcel.recycle(); + mOnSubtitleDataListener.onSubtitleData(mMediaPlayer, data); + } + return; + case MEDIA_NOP: // interface test message - ignore break; @@ -2283,6 +2512,30 @@ public class MediaPlayer private OnTimedTextListener mOnTimedTextListener; + /** + * Interface definition of a callback to be invoked when a + * track has data available. + * + * @hide + */ + public interface OnSubtitleDataListener + { + public void onSubtitleData(MediaPlayer mp, SubtitleData data); + } + + /** + * Register a callback to be invoked when a track has data available. + * + * @param listener the callback that will be run + * + * @hide + */ + public void setOnSubtitleDataListener(OnSubtitleDataListener listener) + { + mOnSubtitleDataListener = listener; + } + + private OnSubtitleDataListener mOnSubtitleDataListener; /* Do not change these values without updating their counterparts * in include/media/mediaplayer.h! @@ -2414,6 +2667,12 @@ public class MediaPlayer */ public static final int MEDIA_INFO_METADATA_UPDATE = 802; + /** A new set of external-only metadata is available. Used by + * JAVA framework to avoid triggering track scanning. + * @hide + */ + public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; + /** Failed to handle timed text track properly. * @see android.media.MediaPlayer.OnInfoListener * @@ -2421,6 +2680,16 @@ public class MediaPlayer */ public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; + /** Subtitle track was not supported by the media framework. + * @see android.media.MediaPlayer.OnInfoListener + */ + public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; + + /** Reading the subtitle track takes too long. + * @see android.media.MediaPlayer.OnInfoListener + */ + public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; + /** * Interface definition of a callback to be invoked to communicate some * info and/or warning about the media or its playback. @@ -2441,6 +2710,8 @@ public class MediaPlayer *

        • {@link #MEDIA_INFO_BAD_INTERLEAVING} *
        • {@link #MEDIA_INFO_NOT_SEEKABLE} *
        • {@link #MEDIA_INFO_METADATA_UPDATE} + *
        • {@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE} + *
        • {@link #MEDIA_INFO_SUBTITLE_TIMED_OUT} *
        * @param extra an extra code, specific to the info. Typically * implementation dependent. @@ -2523,4 +2794,390 @@ public class MediaPlayer } private native void updateProxyConfig(ProxyProperties props); + + /** @hide */ + static class TimeProvider implements MediaPlayer.OnSeekCompleteListener, + MediaTimeProvider { + private static final String TAG = "MTP"; + private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L; + private static final long MAX_EARLY_CALLBACK_US = 1000; + private static final long TIME_ADJUSTMENT_RATE = 2; /* meaning 1/2 */ + private long mLastTimeUs = 0; + private MediaPlayer mPlayer; + private boolean mPaused = true; + private boolean mStopped = true; + private long mLastReportedTime; + private long mTimeAdjustment; + // since we are expecting only a handful listeners per stream, there is + // no need for log(N) search performance + private MediaTimeProvider.OnMediaTimeListener mListeners[]; + private long mTimes[]; + private long mLastNanoTime; + private Handler mEventHandler; + private boolean mRefresh = false; + private boolean mPausing = false; + private boolean mSeeking = false; + private static final int NOTIFY = 1; + private static final int NOTIFY_TIME = 0; + private static final int REFRESH_AND_NOTIFY_TIME = 1; + private static final int NOTIFY_STOP = 2; + private static final int NOTIFY_SEEK = 3; + private HandlerThread mHandlerThread; + + /** @hide */ + public boolean DEBUG = false; + + public TimeProvider(MediaPlayer mp) { + mPlayer = mp; + try { + getCurrentTimeUs(true, false); + } catch (IllegalStateException e) { + // we assume starting position + mRefresh = true; + } + + Looper looper; + if ((looper = Looper.myLooper()) == null && + (looper = Looper.getMainLooper()) == null) { + // Create our own looper here in case MP was created without one + mHandlerThread = new HandlerThread("MediaPlayerMTPEventThread", + Process.THREAD_PRIORITY_FOREGROUND); + mHandlerThread.start(); + looper = mHandlerThread.getLooper(); + } + mEventHandler = new EventHandler(looper); + + mListeners = new MediaTimeProvider.OnMediaTimeListener[0]; + mTimes = new long[0]; + mLastTimeUs = 0; + mTimeAdjustment = 0; + } + + private void scheduleNotification(int type, long delayUs) { + // ignore time notifications until seek is handled + if (mSeeking && + (type == NOTIFY_TIME || type == REFRESH_AND_NOTIFY_TIME)) { + return; + } + + if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs); + mStopped = type == NOTIFY_STOP; + mSeeking = type == NOTIFY_SEEK; + mEventHandler.removeMessages(NOTIFY); + Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0); + mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000)); + } + + /** @hide */ + public void close() { + mEventHandler.removeMessages(NOTIFY); + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread = null; + } + } + + /** @hide */ + protected void finalize() { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + } + } + + /** @hide */ + public void onPaused(boolean paused) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "onPaused: " + paused); + if (mStopped) { // handle as seek if we were stopped + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } else { + mPausing = paused; // special handling if player disappeared + scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */); + } + } + } + + /** @hide */ + public void onStopped() { + synchronized(this) { + if (DEBUG) Log.d(TAG, "onStopped"); + mPaused = true; + scheduleNotification(NOTIFY_STOP, 0 /* delay */); + } + } + + /** @hide */ + @Override + public void onSeekComplete(MediaPlayer mp) { + synchronized(this) { + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } + + /** @hide */ + public void onNewPlayer() { + if (mRefresh) { + synchronized(this) { + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } + } + + private synchronized void notifySeek() { + mSeeking = false; + try { + long timeUs = getCurrentTimeUs(true, false); + if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs); + + for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { + if (listener == null) { + break; + } + listener.onSeek(timeUs); + } + } catch (IllegalStateException e) { + // we should not be there, but at least signal pause + if (DEBUG) Log.d(TAG, "onSeekComplete but no player"); + mPausing = true; // special handling if player disappeared + notifyTimedEvent(false /* refreshTime */); + } + } + + private synchronized void notifyStop() { + for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { + if (listener == null) { + break; + } + listener.onStop(); + } + } + + private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) { + int i = 0; + for (; i < mListeners.length; i++) { + if (mListeners[i] == listener || mListeners[i] == null) { + break; + } + } + + // new listener + if (i >= mListeners.length) { + MediaTimeProvider.OnMediaTimeListener[] newListeners = + new MediaTimeProvider.OnMediaTimeListener[i + 1]; + long[] newTimes = new long[i + 1]; + System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length); + System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length); + mListeners = newListeners; + mTimes = newTimes; + } + + if (mListeners[i] == null) { + mListeners[i] = listener; + mTimes[i] = MediaTimeProvider.NO_TIME; + } + return i; + } + + public void notifyAt( + long timeUs, MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "notifyAt " + timeUs); + mTimes[registerListener(listener)] = timeUs; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "scheduleUpdate"); + int i = registerListener(listener); + + if (mStopped) { + scheduleNotification(NOTIFY_STOP, 0 /* delay */); + } else { + mTimes[i] = 0; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + } + + public void cancelNotifications( + MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + int i = 0; + for (; i < mListeners.length; i++) { + if (mListeners[i] == listener) { + System.arraycopy(mListeners, i + 1, + mListeners, i, mListeners.length - i - 1); + System.arraycopy(mTimes, i + 1, + mTimes, i, mTimes.length - i - 1); + mListeners[mListeners.length - 1] = null; + mTimes[mTimes.length - 1] = NO_TIME; + break; + } else if (mListeners[i] == null) { + break; + } + } + + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + private synchronized void notifyTimedEvent(boolean refreshTime) { + // figure out next callback + long nowUs; + try { + nowUs = getCurrentTimeUs(refreshTime, true); + } catch (IllegalStateException e) { + // assume we paused until new player arrives + mRefresh = true; + mPausing = true; // this ensures that call succeeds + nowUs = getCurrentTimeUs(refreshTime, true); + } + long nextTimeUs = nowUs; + + if (mSeeking) { + // skip timed-event notifications until seek is complete + return; + } + + if (DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ") + .append(nowUs).append(") from {"); + boolean first = true; + for (long time: mTimes) { + if (time == NO_TIME) { + continue; + } + if (!first) sb.append(", "); + sb.append(time); + first = false; + } + sb.append("}"); + Log.d(TAG, sb.toString()); + } + + Vector activatedListeners = + new Vector(); + for (int ix = 0; ix < mTimes.length; ix++) { + if (mListeners[ix] == null) { + break; + } + if (mTimes[ix] <= NO_TIME) { + // ignore, unless we were stopped + } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) { + activatedListeners.add(mListeners[ix]); + if (DEBUG) Log.d(TAG, "removed"); + mTimes[ix] = NO_TIME; + } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) { + nextTimeUs = mTimes[ix]; + } + } + + if (nextTimeUs > nowUs && !mPaused) { + // schedule callback at nextTimeUs + if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs); + scheduleNotification(NOTIFY_TIME, nextTimeUs - nowUs); + } else { + mEventHandler.removeMessages(NOTIFY); + // no more callbacks + } + + for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) { + listener.onTimedEvent(nowUs); + } + } + + private long getEstimatedTime(long nanoTime, boolean monotonic) { + if (mPaused) { + mLastReportedTime = mLastTimeUs + mTimeAdjustment; + } else { + long timeSinceRead = (nanoTime - mLastNanoTime) / 1000; + mLastReportedTime = mLastTimeUs + timeSinceRead; + if (mTimeAdjustment > 0) { + long adjustment = + mTimeAdjustment - timeSinceRead / TIME_ADJUSTMENT_RATE; + if (adjustment <= 0) { + mTimeAdjustment = 0; + } else { + mLastReportedTime += adjustment; + } + } + } + return mLastReportedTime; + } + + public long getCurrentTimeUs(boolean refreshTime, boolean monotonic) + throws IllegalStateException { + synchronized (this) { + // we always refresh the time when the paused-state changes, because + // we expect to have received the pause-change event delayed. + if (mPaused && !refreshTime) { + return mLastReportedTime; + } + + long nanoTime = System.nanoTime(); + if (refreshTime || + nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) { + try { + mLastTimeUs = mPlayer.getCurrentPosition() * 1000; + mPaused = !mPlayer.isPlaying(); + if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); + } catch (IllegalStateException e) { + if (mPausing) { + // if we were pausing, get last estimated timestamp + mPausing = false; + getEstimatedTime(nanoTime, monotonic); + mPaused = true; + if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime); + return mLastReportedTime; + } + // TODO get time when prepared + throw e; + } + mLastNanoTime = nanoTime; + if (monotonic && mLastTimeUs < mLastReportedTime) { + /* have to adjust time */ + mTimeAdjustment = mLastReportedTime - mLastTimeUs; + if (mTimeAdjustment > 1000000) { + // schedule seeked event if time jumped significantly + // TODO: do this properly by introducing an exception + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } else { + mTimeAdjustment = 0; + } + } + + return getEstimatedTime(nanoTime, monotonic); + } + } + + private class EventHandler extends Handler { + public EventHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == NOTIFY) { + switch (msg.arg1) { + case NOTIFY_TIME: + notifyTimedEvent(false /* refreshTime */); + break; + case REFRESH_AND_NOTIFY_TIME: + notifyTimedEvent(true /* refreshTime */); + break; + case NOTIFY_STOP: + notifyStop(); + break; + case NOTIFY_SEEK: + notifySeek(); + break; + } + } + } + } + } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 3e688db861b5eb49fd36c758d45e160ef6249021..8dcbd6b6f8d9ead66f9fd5d1e782e4eeb6fda361 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -179,6 +179,40 @@ public class MediaRecorder * is applied. */ public static final int VOICE_COMMUNICATION = 7; + + /** + * Audio source for a submix of audio streams to be presented remotely. + *

        + * An application can use this audio source to capture a mix of audio streams + * that should be transmitted to a remote receiver such as a Wifi display. + * While recording is active, these audio streams are redirected to the remote + * submix instead of being played on the device speaker or headset. + *

        + * Certain streams are excluded from the remote submix, including + * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_ALARM}, + * and {@link AudioManager#STREAM_NOTIFICATION}. These streams will continue + * to be presented locally as usual. + *

        + * Capturing the remote submix audio requires the + * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission. + * This permission is reserved for use by system components and is not available to + * third-party applications. + *

        + */ + public static final int REMOTE_SUBMIX = 8; + + /** + * Audio source for preemptible, low-priority software hotword detection + * It presents the same gain and pre processing tuning as {@link #VOICE_RECOGNITION}. + *

        + * An application should use this audio source when it wishes to do + * always-on software hotword detection, while gracefully giving in to any other application + * that might want to read from the microphone. + *

        + * This is a hidden audio source. + * @hide + */ + protected static final int HOTWORD = 1999; } /** @@ -294,7 +328,7 @@ public class MediaRecorder * @see android.media.MediaRecorder.AudioSource */ public static final int getAudioSourceMax() { - return AudioSource.VOICE_COMMUNICATION; + return AudioSource.REMOTE_SUBMIX; } /** diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 5c58503c6b7ef1ad5b4781cf9900d58383e81d6a..9a79c94119e3eae4a8148a099a43a8339c225877 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -16,6 +16,7 @@ package android.media; +import android.app.ActivityThread; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -653,7 +654,13 @@ public class MediaRouter { if (info == sStatic.mSelectedRoute) { // Removing the currently selected route? Select the default before we remove it. // TODO: Be smarter about the route types here; this selects for all valid. - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo); + if (info != sStatic.mBluetoothA2dpRoute && sStatic.mBluetoothA2dpRoute != null) { + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, + sStatic.mBluetoothA2dpRoute); + } else { + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, + sStatic.mDefaultAudioVideo); + } } if (!found) { sStatic.mCategories.remove(removingCat); @@ -875,44 +882,45 @@ public class MediaRouter { boolean wantScan = false; boolean blockScan = false; WifiDisplay[] oldDisplays = oldStatus != null ? - oldStatus.getRememberedDisplays() : WifiDisplay.EMPTY_ARRAY; + oldStatus.getDisplays() : WifiDisplay.EMPTY_ARRAY; WifiDisplay[] newDisplays; - WifiDisplay[] availableDisplays; WifiDisplay activeDisplay; if (newStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { - newDisplays = newStatus.getRememberedDisplays(); - availableDisplays = newStatus.getAvailableDisplays(); + newDisplays = newStatus.getDisplays(); activeDisplay = newStatus.getActiveDisplay(); } else { - newDisplays = availableDisplays = WifiDisplay.EMPTY_ARRAY; + newDisplays = WifiDisplay.EMPTY_ARRAY; activeDisplay = null; } for (int i = 0; i < newDisplays.length; i++) { final WifiDisplay d = newDisplays[i]; - final boolean available = findMatchingDisplay(d, availableDisplays) != null; - RouteInfo route = findWifiDisplayRoute(d); - if (route == null) { - route = makeWifiDisplayRoute(d, available); - addRouteStatic(route); - wantScan = true; - } else { - updateWifiDisplayRoute(route, d, available, newStatus); - } - if (d.equals(activeDisplay)) { - selectRouteStatic(route.getSupportedTypes(), route); + if (d.isRemembered()) { + RouteInfo route = findWifiDisplayRoute(d); + if (route == null) { + route = makeWifiDisplayRoute(d, newStatus); + addRouteStatic(route); + wantScan = true; + } else { + updateWifiDisplayRoute(route, d, newStatus); + } + if (d.equals(activeDisplay)) { + selectRouteStatic(route.getSupportedTypes(), route); - // Don't scan if we're already connected to a wifi display, - // the scanning process can cause a hiccup with some configurations. - blockScan = true; + // Don't scan if we're already connected to a wifi display, + // the scanning process can cause a hiccup with some configurations. + blockScan = true; + } } } for (int i = 0; i < oldDisplays.length; i++) { final WifiDisplay d = oldDisplays[i]; - final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays); - if (newDisplay == null) { - removeRoute(findWifiDisplayRoute(d)); + if (d.isRemembered()) { + final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays); + if (newDisplay == null || !newDisplay.isRemembered()) { + removeRoute(findWifiDisplayRoute(d)); + } } } @@ -923,42 +931,20 @@ public class MediaRouter { sStatic.mLastKnownWifiDisplayStatus = newStatus; } - static RouteInfo makeWifiDisplayRoute(WifiDisplay display, boolean available) { - final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory); - newRoute.mDeviceAddress = display.getDeviceAddress(); - newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; - newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; - newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; - - newRoute.setStatusCode(available ? - RouteInfo.STATUS_AVAILABLE : RouteInfo.STATUS_CONNECTING); - newRoute.mEnabled = available; - - newRoute.mName = display.getFriendlyDisplayName(); - newRoute.mDescription = sStatic.mResources.getText( - com.android.internal.R.string.wireless_display_route_description); - - newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, - sStatic.getAllPresentationDisplays()); - return newRoute; - } - - private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display, - boolean available, WifiDisplayStatus wifiDisplayStatus) { - final boolean isScanning = - wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING; - - boolean changed = false; + static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) { int newStatus = RouteInfo.STATUS_NONE; - if (available) { - newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE; + if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) { + newStatus = RouteInfo.STATUS_SCANNING; + } else if (d.isAvailable()) { + newStatus = d.canConnect() ? + RouteInfo.STATUS_AVAILABLE: RouteInfo.STATUS_IN_USE; } else { newStatus = RouteInfo.STATUS_NOT_AVAILABLE; } - if (display.equals(wifiDisplayStatus.getActiveDisplay())) { - final int activeState = wifiDisplayStatus.getActiveDisplayState(); + if (d.equals(wfdStatus.getActiveDisplay())) { + final int activeState = wfdStatus.getActiveDisplayState(); switch (activeState) { case WifiDisplayStatus.DISPLAY_STATE_CONNECTED: newStatus = RouteInfo.STATUS_NONE; @@ -972,22 +958,51 @@ public class MediaRouter { } } + return newStatus; + } + + static boolean isWifiDisplayEnabled(WifiDisplay d, WifiDisplayStatus wfdStatus) { + return d.isAvailable() && (d.canConnect() || d.equals(wfdStatus.getActiveDisplay())); + } + + static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) { + final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory); + newRoute.mDeviceAddress = display.getDeviceAddress(); + newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; + newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; + newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; + + newRoute.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus); + newRoute.mName = display.getFriendlyDisplayName(); + newRoute.mDescription = sStatic.mResources.getText( + com.android.internal.R.string.wireless_display_route_description); + + newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, + sStatic.getAllPresentationDisplays()); + return newRoute; + } + + private static void updateWifiDisplayRoute( + RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus) { + boolean changed = false; final String newName = display.getFriendlyDisplayName(); if (!route.getName().equals(newName)) { route.mName = newName; changed = true; } - changed |= route.mEnabled != available; - route.mEnabled = available; + boolean enabled = isWifiDisplayEnabled(display, wfdStatus); + changed |= route.mEnabled != enabled; + route.mEnabled = enabled; - changed |= route.setStatusCode(newStatus); + changed |= route.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); if (changed) { dispatchRouteChanged(route); } - if (!available && route == sStatic.mSelectedRoute) { + if (!enabled && route == sStatic.mSelectedRoute) { // Oops, no longer available. Reselect the default. final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo; selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute); @@ -1068,6 +1083,7 @@ public class MediaRouter { /** @hide */ public static final int STATUS_CONNECTING = 2; /** @hide */ public static final int STATUS_AVAILABLE = 3; /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4; + /** @hide */ public static final int STATUS_IN_USE = 5; private Object mTag; @@ -1179,6 +1195,9 @@ public class MediaRouter { case STATUS_NOT_AVAILABLE: resId = com.android.internal.R.string.media_route_status_not_available; break; + case STATUS_IN_USE: + resId = com.android.internal.R.string.media_route_status_in_use; + break; } mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; return true; @@ -1292,7 +1311,8 @@ public class MediaRouter { public void requestSetVolume(int volume) { if (mPlaybackType == PLAYBACK_TYPE_LOCAL) { try { - sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0); + sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0, + ActivityThread.currentPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error setting local stream volume", e); } @@ -1312,7 +1332,8 @@ public class MediaRouter { try { final int volume = Math.max(0, Math.min(getVolume() + direction, getVolumeMax())); - sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0); + sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0, + ActivityThread.currentPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error setting local stream volume", e); } diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 21b6e14d34130ecab8f3c08b18f228fad1f86554..273eb64aad7d58b5ea3bcbf2db352f86a0237398 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -113,6 +113,9 @@ public class MediaScannerConnection implements ServiceConnection { synchronized (this) { if (!mConnected) { Intent intent = new Intent(IMediaScannerService.class.getName()); + intent.setComponent( + new ComponentName("com.android.providers.media", + "com.android.providers.media.MediaScannerService")); mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); mConnected = true; } diff --git a/media/java/android/media/MediaTimeProvider.java b/media/java/android/media/MediaTimeProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..fe377125e7bee321b8ba44ce7876fdc141d4baec --- /dev/null +++ b/media/java/android/media/MediaTimeProvider.java @@ -0,0 +1,90 @@ +/* + * 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.media; + +/** @hide */ +public interface MediaTimeProvider { + // we do not allow negative media time + /** + * Presentation time value if no timed event notification is requested. + */ + public final static long NO_TIME = -1; + + /** + * Cancels all previous notification request from this listener if any. It + * registers the listener to get seek and stop notifications. If timeUs is + * not negative, it also registers the listener for a timed event + * notification when the presentation time reaches (becomes greater) than + * the value specified. This happens immediately if the current media time + * is larger than or equal to timeUs. + * + * @param timeUs presentation time to get timed event callback at (or + * {@link #NO_TIME}) + */ + public void notifyAt(long timeUs, OnMediaTimeListener listener); + + /** + * Cancels all previous notification request from this listener if any. It + * registers the listener to get seek and stop notifications. If the media + * is stopped, the listener will immediately receive a stop notification. + * Otherwise, it will receive a timed event notificaton. + */ + public void scheduleUpdate(OnMediaTimeListener listener); + + /** + * Cancels all previous notification request from this listener if any. + */ + public void cancelNotifications(OnMediaTimeListener listener); + + /** + * Get the current presentation time. + * + * @param precise Whether getting a precise time is important. This is + * more costly. + * @param monotonic Whether returned time should be monotonic: that is, + * greater than or equal to the last returned time. Don't + * always set this to true. E.g. this has undesired + * consequences if the media is seeked between calls. + * @throws IllegalStateException if the media is not initialized + */ + public long getCurrentTimeUs(boolean precise, boolean monotonic) + throws IllegalStateException; + + /** @hide */ + public static interface OnMediaTimeListener { + /** + * Called when the registered time was reached naturally. + * + * @param timeUs current media time + */ + void onTimedEvent(long timeUs); + + /** + * Called when the media time changed due to seeking. + * + * @param timeUs current media time + */ + void onSeek(long timeUs); + + /** + * Called when the playback stopped. This is not called on pause, only + * on full stop, at which point there is no further current media time. + */ + void onStop(); + } +} + diff --git a/media/java/android/media/Rating.aidl b/media/java/android/media/Rating.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1dc336aeb79d548869e5f2d332f14480ac6a0db3 --- /dev/null +++ b/media/java/android/media/Rating.aidl @@ -0,0 +1,19 @@ +/* + * 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.media; + +parcelable Rating; diff --git a/media/java/android/media/Rating.java b/media/java/android/media/Rating.java new file mode 100644 index 0000000000000000000000000000000000000000..82c039262e2b992ed808d220fc82875d3e327466 --- /dev/null +++ b/media/java/android/media/Rating.java @@ -0,0 +1,284 @@ +/* + * 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.media; + +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * A class to encapsulate rating information used as content metadata. + * A rating is defined by its rating style (see {@link #RATING_HEART}, + * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, + * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may + * be defined as "unrated"), both of which are defined when the rating instance is constructed + * through one of the factory methods. + */ +public final class Rating implements Parcelable { + + private final static String TAG = "Rating"; + + /** + * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to + * indicate the content referred to is a favorite (or not). + */ + public final static int RATING_HEART = 1; + + /** + * A rating style for "thumb up" vs "thumb down". + */ + public final static int RATING_THUMB_UP_DOWN = 2; + + /** + * A rating style with 0 to 3 stars. + */ + public final static int RATING_3_STARS = 3; + + /** + * A rating style with 0 to 4 stars. + */ + public final static int RATING_4_STARS = 4; + + /** + * A rating style with 0 to 5 stars. + */ + public final static int RATING_5_STARS = 5; + + /** + * A rating style expressed as a percentage. + */ + public final static int RATING_PERCENTAGE = 6; + + private final static float RATING_NOT_RATED = -1.0f; + + private final int mRatingStyle; + + private final float mRatingValue; + + private Rating(int ratingStyle, float rating) { + mRatingStyle = ratingStyle; + mRatingValue = rating; + } + + + /** + * @hide + */ + @Override + public String toString () { + return "Rating:style=" + mRatingStyle + " rating=" + + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue)); + } + + @Override + public int describeContents() { + return mRatingStyle; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRatingStyle); + dest.writeFloat(mRatingValue); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + /** + * Rebuilds a Rating previously stored with writeToParcel(). + * @param p Parcel object to read the Rating from + * @return a new Rating created from the data in the parcel + */ + public Rating createFromParcel(Parcel p) { + return new Rating(p.readInt(), p.readFloat()); + } + public Rating[] newArray(int size) { + return new Rating[size]; + } + }; + + /** + * Return a Rating instance with no rating. + * Create and return a new Rating instance with no rating known for the given + * rating style. + * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, + * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, + * or {@link #RATING_PERCENTAGE}. + * @return null if an invalid rating style is passed, a new Rating instance otherwise. + */ + public static Rating newUnratedRating(int ratingStyle) { + switch(ratingStyle) { + case RATING_HEART: + case RATING_THUMB_UP_DOWN: + case RATING_3_STARS: + case RATING_4_STARS: + case RATING_5_STARS: + case RATING_PERCENTAGE: + return new Rating(ratingStyle, RATING_NOT_RATED); + default: + return null; + } + } + + /** + * Return a Rating instance with a heart-based rating. + * Create and return a new Rating instance with a rating style of {@link #RATING_HEART}, + * and a heart-based rating. + * @param hasHeart true for a "heart selected" rating, false for "heart unselected". + * @return a new Rating instance. + */ + public static Rating newHeartRating(boolean hasHeart) { + return new Rating(RATING_HEART, hasHeart ? 1.0f : 0.0f); + } + + /** + * Return a Rating instance with a thumb-based rating. + * Create and return a new Rating instance with a {@link #RATING_THUMB_UP_DOWN} + * rating style, and a "thumb up" or "thumb down" rating. + * @param thumbIsUp true for a "thumb up" rating, false for "thumb down". + * @return a new Rating instance. + */ + public static Rating newThumbRating(boolean thumbIsUp) { + return new Rating(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f); + } + + /** + * Return a Rating instance with a star-based rating. + * Create and return a new Rating instance with one of the star-base rating styles + * and the given integer or fractional number of stars. Non integer values can for instance + * be used to represent an average rating value, which might not be an integer number of stars. + * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, + * {@link #RATING_5_STARS}. + * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to + * the rating style. + * @return null if the rating style is invalid, or the rating is out of range, + * a new Rating instance otherwise. + */ + public static Rating newStarRating(int starRatingStyle, float starRating) { + float maxRating = -1.0f; + switch(starRatingStyle) { + case RATING_3_STARS: + maxRating = 3.0f; + break; + case RATING_4_STARS: + maxRating = 4.0f; + break; + case RATING_5_STARS: + maxRating = 5.0f; + break; + default: + Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating"); + return null; + } + if ((starRating < 0.0f) || (starRating > maxRating)) { + Log.e(TAG, "Trying to set out of range star-based rating"); + return null; + } + return new Rating(starRatingStyle, starRating); + } + + /** + * Return a Rating instance with a percentage-based rating. + * Create and return a new Rating instance with a {@link #RATING_PERCENTAGE} + * rating style, and a rating of the given percentage. + * @param percent the value of the rating + * @return null if the rating is out of range, a new Rating instance otherwise. + */ + public static Rating newPercentageRating(float percent) { + if ((percent < 0.0f) || (percent > 100.0f)) { + Log.e(TAG, "Invalid percentage-based rating value"); + return null; + } else { + return new Rating(RATING_PERCENTAGE, percent); + } + } + + /** + * Return whether there is a rating value available. + * @return true if the instance was not created with {@link #newUnratedRating(int)}. + */ + public boolean isRated() { + return mRatingValue >= 0.0f; + } + + /** + * Return the rating style. + * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, + * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, + * or {@link #RATING_PERCENTAGE}. + */ + public int getRatingStyle() { + return mRatingStyle; + } + + /** + * Return whether the rating is "heart selected". + * @return true if the rating is "heart selected", false if the rating is "heart unselected", + * if the rating style is not {@link #RATING_HEART} or if it is unrated. + */ + public boolean hasHeart() { + if (mRatingStyle != RATING_HEART) { + return false; + } else { + return (mRatingValue == 1.0f); + } + } + + /** + * Return whether the rating is "thumb up". + * @return true if the rating is "thumb up", false if the rating is "thumb down", + * if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated. + */ + public boolean isThumbUp() { + if (mRatingStyle != RATING_THUMB_UP_DOWN) { + return false; + } else { + return (mRatingValue == 1.0f); + } + } + + /** + * Return the star-based rating value. + * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is + * not star-based, or if it is unrated. + */ + public float getStarRating() { + switch (mRatingStyle) { + case RATING_3_STARS: + case RATING_4_STARS: + case RATING_5_STARS: + if (isRated()) { + return mRatingValue; + } + default: + return -1.0f; + } + } + + /** + * Return the percentage-based rating value. + * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is + * not percentage-based, or if it is unrated. + */ + public float getPercentRating() { + if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) { + return -1.0f; + } else { + return mRatingValue; + } + } +} \ No newline at end of file diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 7379438360ab156e4c2b5df31c52593419f67c9d..0c00abaa93ff880dd705379b3ab3f5e46b2f1064 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -293,6 +294,17 @@ public class RemoteControlClient * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; + /** + * Flag indicating a RemoteControlClient supports ratings. + * This flag must be set in order for components that display the RemoteControlClient + * information, to display ratings information, and, if ratings are declared editable + * (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the + * {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate + * the media, with values being received through the interface set with + * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}. + * @see #setTransportControlFlags(int) + */ + public final static int FLAG_KEY_MEDIA_RATING = 1 << 9; /** * @hide @@ -374,23 +386,6 @@ public class RemoteControlClient mEventHandler = new EventHandler(this, looper); } - private static final int[] METADATA_KEYS_TYPE_STRING = { - MediaMetadataRetriever.METADATA_KEY_ALBUM, - MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, - MediaMetadataRetriever.METADATA_KEY_TITLE, - MediaMetadataRetriever.METADATA_KEY_ARTIST, - MediaMetadataRetriever.METADATA_KEY_AUTHOR, - MediaMetadataRetriever.METADATA_KEY_COMPILATION, - MediaMetadataRetriever.METADATA_KEY_COMPOSER, - MediaMetadataRetriever.METADATA_KEY_DATE, - MediaMetadataRetriever.METADATA_KEY_GENRE, - MediaMetadataRetriever.METADATA_KEY_TITLE, - MediaMetadataRetriever.METADATA_KEY_WRITER }; - private static final int[] METADATA_KEYS_TYPE_LONG = { - MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, - MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, - MediaMetadataRetriever.METADATA_KEY_DURATION }; - /** * Class used to modify metadata in a {@link RemoteControlClient} object. * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, @@ -399,24 +394,7 @@ public class RemoteControlClient * for the associated client. Once the metadata has been "applied", you cannot reuse this * instance of the MetadataEditor. */ - public class MetadataEditor { - /** - * @hide - */ - protected boolean mMetadataChanged; - /** - * @hide - */ - protected boolean mArtworkChanged; - /** - * @hide - */ - protected Bitmap mEditorArtwork; - /** - * @hide - */ - protected Bundle mEditorMetadata; - private boolean mApplied = false; + public class MetadataEditor extends MediaMetadataEditor { // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance private MetadataEditor() { } @@ -431,9 +409,10 @@ public class RemoteControlClient * The metadata key for the content artwork / album art. */ public final static int BITMAP_KEY_ARTWORK = 100; + /** * @hide - * TODO(jmtrivi) have lockscreen and music move to the new key name + * TODO(jmtrivi) have lockscreen move to the new key name and remove */ public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; @@ -460,15 +439,7 @@ public class RemoteControlClient */ public synchronized MetadataEditor putString(int key, String value) throws IllegalArgumentException { - if (mApplied) { - Log.e(TAG, "Can't edit a previously applied MetadataEditor"); - return this; - } - if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) { - throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); - } - mEditorMetadata.putString(String.valueOf(key), value); - mMetadataChanged = true; + super.putString(key, value); return this; } @@ -489,15 +460,7 @@ public class RemoteControlClient */ public synchronized MetadataEditor putLong(int key, long value) throws IllegalArgumentException { - if (mApplied) { - Log.e(TAG, "Can't edit a previously applied MetadataEditor"); - return this; - } - if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) { - throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); - } - mEditorMetadata.putLong(String.valueOf(key), value); - mMetadataChanged = true; + super.putLong(key, value); return this; } @@ -511,31 +474,22 @@ public class RemoteControlClient * @throws IllegalArgumentException * @see android.graphics.Bitmap */ + @Override public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) throws IllegalArgumentException { - if (mApplied) { - Log.e(TAG, "Can't edit a previously applied MetadataEditor"); - return this; - } - if (key != BITMAP_KEY_ARTWORK) { - throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); - } - mEditorArtwork = bitmap; - mArtworkChanged = true; + super.putBitmap(key, bitmap); return this; } /** - * Clears all the metadata that has been set since the MetadataEditor instance was - * created with {@link RemoteControlClient#editMetadata(boolean)}. + * Clears all the metadata that has been set since the MetadataEditor instance was created + * (with {@link RemoteControlClient#editMetadata(boolean)}). + * Note that clearing the metadata doesn't reset the editable keys + * (use {@link MediaMetadataEditor#removeEditableKeys()} instead). */ + @Override public synchronized void clear() { - if (mApplied) { - Log.e(TAG, "Can't clear a previously applied MetadataEditor"); - return; - } - mEditorMetadata.clear(); - mEditorArtwork = null; + super.clear(); } /** @@ -552,6 +506,8 @@ public class RemoteControlClient synchronized(mCacheLock) { // assign the edited data mMetadata = new Bundle(mEditorMetadata); + // add the information about editable keys + mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys); if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { mOriginalArtwork.recycle(); } @@ -559,13 +515,13 @@ public class RemoteControlClient mEditorArtwork = null; if (mMetadataChanged & mArtworkChanged) { // send to remote control display if conditions are met - sendMetadataWithArtwork_syncCacheLock(); + sendMetadataWithArtwork_syncCacheLock(null, 0, 0); } else if (mMetadataChanged) { // send to remote control display if conditions are met - sendMetadata_syncCacheLock(); + sendMetadata_syncCacheLock(null); } else if (mArtworkChanged) { // send to remote control display if conditions are met - sendArtwork_syncCacheLock(); + sendArtwork_syncCacheLock(null, 0, 0); } mApplied = true; } @@ -585,6 +541,7 @@ public class RemoteControlClient editor.mEditorArtwork = null; editor.mMetadataChanged = true; editor.mArtworkChanged = true; + editor.mEditableKeys = 0; } else { editor.mEditorMetadata = new Bundle(mMetadata); editor.mEditorArtwork = mOriginalArtwork; @@ -663,7 +620,7 @@ public class RemoteControlClient mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); // send to remote control display if conditions are met - sendPlaybackState_syncCacheLock(); + sendPlaybackState_syncCacheLock(null); // update AudioService sendAudioServiceNewPlaybackState_syncCacheLock(); @@ -739,7 +696,8 @@ public class RemoteControlClient * {@link #FLAG_KEY_MEDIA_STOP}, * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, * {@link #FLAG_KEY_MEDIA_NEXT}, - * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE} + * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}, + * {@link #FLAG_KEY_MEDIA_RATING}. */ public void setTransportControlFlags(int transportControlFlags) { synchronized(mCacheLock) { @@ -747,10 +705,39 @@ public class RemoteControlClient mTransportControlFlags = transportControlFlags; // send to remote control display if conditions are met - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(null); + } + } + + /** + * Interface definition for a callback to be invoked when one of the metadata values has + * been updated. + * Implement this interface to receive metadata updates after registering your listener + * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}. + */ + public interface OnMetadataUpdateListener { + /** + * Called on the implementer to notify that the metadata field for the given key has + * been updated to the new value. + * @param key the identifier of the updated metadata field. + * @param newValue the Object storing the new value for the key. + */ + public abstract void onMetadataUpdate(int key, Object newValue); + } + + /** + * Sets the listener to be called whenever the metadata is updated. + * New metadata values will be received in the same thread as the one in which + * RemoteControlClient was created. + * @param l the metadata update listener + */ + public void setMetadataUpdateListener(OnMetadataUpdateListener l) { + synchronized(mCacheLock) { + mMetadataUpdateListener = l; } } + /** * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. @@ -804,7 +791,7 @@ public class RemoteControlClient mPositionUpdateListener = l; if (oldCapa != mPlaybackPositionCapabilities) { // tell RCDs that this RCC's playback position capabilities have changed - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(null); } } } @@ -826,7 +813,7 @@ public class RemoteControlClient mPositionProvider = l; if (oldCapa != mPlaybackPositionCapabilities) { // tell RCDs that this RCC's playback position capabilities have changed - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(null); } if ((mPositionProvider != null) && (mEventHandler != null) && playbackPositionShouldMove(mPlaybackState)) { @@ -1022,6 +1009,11 @@ public class RemoteControlClient * Provider registered by user of RemoteControlClient to provide the current playback position. */ private OnGetPlaybackPositionListener mPositionProvider; + /** + * Listener registered by user of RemoteControlClient to receive edit changes to metadata + * it exposes. + */ + private OnMetadataUpdateListener mMetadataUpdateListener; /** * The current remote control client generation ID across the system, as known by this object */ @@ -1057,6 +1049,7 @@ public class RemoteControlClient private int mArtworkExpectedWidth; private int mArtworkExpectedHeight; private boolean mWantsPositionSync = false; + private boolean mEnabled = true; DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) { mRcDisplay = rcd; @@ -1091,6 +1084,7 @@ public class RemoteControlClient */ private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { + //TODO change name to informationRequestForAllDisplays() public void onInformationRequested(int generationId, int infoFlags) { // only post messages, we can't block here if (mEventHandler != null) { @@ -1104,12 +1098,30 @@ public class RemoteControlClient mEventHandler.removeMessages(MSG_REQUEST_METADATA); mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL); mEventHandler.removeMessages(MSG_REQUEST_ARTWORK); + mEventHandler.removeMessages(MSG_REQUEST_METADATA_ARTWORK); mEventHandler.sendMessage( - mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE)); + mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, null)); mEventHandler.sendMessage( - mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL)); - mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA)); - mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK)); + mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, null)); + mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK, + 0, 0, null)); + } + } + + public void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, rcd)); + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, rcd)); + if ((w > 0) && (h > 0)) { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK, w, h, rcd)); + } else { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_METADATA, rcd)); + } } } @@ -1154,6 +1166,14 @@ public class RemoteControlClient } } + public void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled) { + // only post messages, we can't block here + if ((mEventHandler != null) && (rcd != null)) { + mEventHandler.sendMessage(mEventHandler.obtainMessage( + MSG_DISPLAY_ENABLE, enabled ? 1 : 0, 0/*arg2 ignored*/, rcd)); + } + } + public void seekTo(int generationId, long timeMs) { // only post messages, we can't block here if (mEventHandler != null) { @@ -1163,6 +1183,14 @@ public class RemoteControlClient new Long(timeMs))); } } + + public void updateMetadata(int generationId, int key, Rating value) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.sendMessage(mEventHandler.obtainMessage( + MSG_UPDATE_METADATA, generationId /* arg1 */, key /* arg2*/, value)); + } + } }; /** @@ -1206,6 +1234,9 @@ public class RemoteControlClient private final static int MSG_SEEK_TO = 10; private final static int MSG_POSITION_DRIFT_CHECK = 11; private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12; + private final static int MSG_UPDATE_METADATA = 13; + private final static int MSG_REQUEST_METADATA_ARTWORK = 14; + private final static int MSG_DISPLAY_ENABLE = 15; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -1217,22 +1248,29 @@ public class RemoteControlClient switch(msg.what) { case MSG_REQUEST_PLAYBACK_STATE: synchronized (mCacheLock) { - sendPlaybackState_syncCacheLock(); + sendPlaybackState_syncCacheLock((IRemoteControlDisplay)msg.obj); } break; case MSG_REQUEST_METADATA: synchronized (mCacheLock) { - sendMetadata_syncCacheLock(); + sendMetadata_syncCacheLock((IRemoteControlDisplay)msg.obj); } break; case MSG_REQUEST_TRANSPORTCONTROL: synchronized (mCacheLock) { - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock((IRemoteControlDisplay)msg.obj); } break; case MSG_REQUEST_ARTWORK: synchronized (mCacheLock) { - sendArtwork_syncCacheLock(); + sendArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj, + msg.arg1, msg.arg2); + } + break; + case MSG_REQUEST_METADATA_ARTWORK: + synchronized (mCacheLock) { + sendMetadataWithArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj, + msg.arg1, msg.arg2); } break; case MSG_NEW_INTERNAL_CLIENT_GEN: @@ -1259,6 +1297,12 @@ public class RemoteControlClient case MSG_DISPLAY_WANTS_POS_SYNC: onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); break; + case MSG_UPDATE_METADATA: + onUpdateMetadata(msg.arg1, msg.arg2, msg.obj); + break; + case MSG_DISPLAY_ENABLE: + onDisplayEnable((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); + break; default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -1268,58 +1312,101 @@ public class RemoteControlClient //=========================================================== // Communication with the IRemoteControlDisplay (the displays known to the system) - private void sendPlaybackState_syncCacheLock() { + private void sendPlaybackState_syncCacheLock(IRemoteControlDisplay target) { if (mCurrentClientGenId == mInternalClientGenId) { - final Iterator displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (target != null) { try { - di.mRcDisplay.setPlaybackState(mInternalClientGenId, + target.setPlaybackState(mInternalClientGenId, mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, mPlaybackSpeed); } catch (RemoteException e) { - Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); - displayIterator.remove(); + Log.e(TAG, "Error in setPlaybackState() for dead display " + target, e); + } + return; + } + // target == null implies all displays must be updated + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mEnabled) { + try { + di.mRcDisplay.setPlaybackState(mInternalClientGenId, + mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, + mPlaybackSpeed); + } catch (RemoteException e) { + Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); + displayIterator.remove(); + } } } } } - private void sendMetadata_syncCacheLock() { + private void sendMetadata_syncCacheLock(IRemoteControlDisplay target) { if (mCurrentClientGenId == mInternalClientGenId) { + if (target != null) { + try { + target.setMetadata(mInternalClientGenId, mMetadata); + } catch (RemoteException e) { + Log.e(TAG, "Error in setMetadata() for dead display " + target, e); + } + return; + } + // target == null implies all displays must be updated final Iterator displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); - try { - di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); - } catch (RemoteException e) { - Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); - displayIterator.remove(); + if (di.mEnabled) { + try { + di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + } catch (RemoteException e) { + Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); + displayIterator.remove(); + } } } } } - private void sendTransportControlInfo_syncCacheLock() { + private void sendTransportControlInfo_syncCacheLock(IRemoteControlDisplay target) { if (mCurrentClientGenId == mInternalClientGenId) { - final Iterator displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (target != null) { try { - di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, + target.setTransportControlInfo(mInternalClientGenId, mTransportControlFlags, mPlaybackPositionCapabilities); } catch (RemoteException e) { - Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, + Log.e(TAG, "Error in setTransportControlFlags() for dead display " + target, e); - displayIterator.remove(); + } + return; + } + // target == null implies all displays must be updated + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mEnabled) { + try { + di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, + mTransportControlFlags, mPlaybackPositionCapabilities); + } catch (RemoteException e) { + Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, + e); + displayIterator.remove(); + } } } } } - private void sendArtwork_syncCacheLock() { + private void sendArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) { // FIXME modify to cache all requested sizes? if (mCurrentClientGenId == mInternalClientGenId) { + if (target != null) { + final DisplayInfoForClient di = new DisplayInfoForClient(target, w, h); + sendArtworkToDisplay(di); + return; + } + // target == null implies all displays must be updated final Iterator displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) { @@ -1349,19 +1436,35 @@ public class RemoteControlClient return true; } - private void sendMetadataWithArtwork_syncCacheLock() { + private void sendMetadataWithArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) { // FIXME modify to cache all requested sizes? if (mCurrentClientGenId == mInternalClientGenId) { + if (target != null) { + try { + if ((w > 0) && (h > 0)) { + Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, w, h); + target.setAllMetadata(mInternalClientGenId, mMetadata, artwork); + } else { + target.setMetadata(mInternalClientGenId, mMetadata); + } + } catch (RemoteException e) { + Log.e(TAG, "Error in set(All)Metadata() for dead display " + target, e); + } + return; + } + // target == null implies all displays must be updated final Iterator displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { - if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { - Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, - di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); - di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); - } else { - di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + if (di.mEnabled) { + if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { + Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, + di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); + di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); + } else { + di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + } } } catch (RemoteException e) { Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e); @@ -1496,8 +1599,10 @@ public class RemoteControlClient ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) { di.mArtworkExpectedWidth = w; di.mArtworkExpectedHeight = h; - if (!sendArtworkToDisplay(di)) { - displayIterator.remove(); + if (di.mEnabled) { + if (!sendArtworkToDisplay(di)) { + displayIterator.remove(); + } } break; } @@ -1515,11 +1620,13 @@ public class RemoteControlClient // that gets upated, and whether the list has one entry that wants position sync while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - di.mWantsPositionSync = wantsSync; - } - if (di.mWantsPositionSync) { - newNeedsPositionSync = true; + if (di.mEnabled) { + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mWantsPositionSync = wantsSync; + } + if (di.mWantsPositionSync) { + newNeedsPositionSync = true; + } } } mNeedsPositionSync = newNeedsPositionSync; @@ -1530,6 +1637,19 @@ public class RemoteControlClient } } + /** pre-condition rcd != null */ + private void onDisplayEnable(IRemoteControlDisplay rcd, boolean enable) { + synchronized(mCacheLock) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mEnabled = enable; + } + } + } + } + private void onSeekTo(int generationId, long timeMs) { synchronized (mCacheLock) { if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { @@ -1538,6 +1658,14 @@ public class RemoteControlClient } } + private void onUpdateMetadata(int generationId, int key, Object value) { + synchronized (mCacheLock) { + if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) { + mMetadataUpdateListener.onMetadataUpdate(key, value); + } + } + } + //=========================================================== // Internal utilities @@ -1576,24 +1704,6 @@ public class RemoteControlClient return bitmap; } - /** - * Fast routine to go through an array of allowed keys and return whether the key is part - * of that array - * @param key the key value - * @param validKeys the array of valid keys for a given type - * @return true if the key is part of the array, false otherwise - */ - private static boolean validTypeForKey(int key, int[] validKeys) { - try { - for (int i = 0 ; ; i++) { - if (key == validKeys[i]) { - return true; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - return false; - } - } /** * Returns whether, for the given playback state, the playback position is expected to @@ -1602,7 +1712,7 @@ public class RemoteControlClient * @return true during any form of playback, false if it's not playing anything while in this * playback state */ - private static boolean playbackPositionShouldMove(int playstate) { + static boolean playbackPositionShouldMove(int playstate) { switch(playstate) { case PLAYSTATE_STOPPED: case PLAYSTATE_PAUSED: diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java new file mode 100644 index 0000000000000000000000000000000000000000..7865ec8138de26d5120f9db3904b1651f97f41db --- /dev/null +++ b/media/java/android/media/RemoteController.java @@ -0,0 +1,908 @@ +/* + * 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.media; + +import android.Manifest; +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.media.IRemoteControlDisplay; +import android.media.MediaMetadataEditor; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.KeyEvent; + +import java.lang.ref.WeakReference; + +/** + * The RemoteController class is used to control media playback, display and update media metadata + * and playback status, published by applications using the {@link RemoteControlClient} class. + *

        + * A RemoteController shall be registered through + * {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send + * media event updates to the {@link OnClientUpdateListener} listener set in the class constructor. + * Implement the methods of the interface to receive the information published by the active + * {@link RemoteControlClient} instances. + *
        By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for + * album art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well. + *

        + * Registration requires the {@link OnClientUpdateListener} listener to be one of the enabled + * notification listeners (see {@link android.service.notification.NotificationListenerService}). + */ +public final class RemoteController +{ + private final static int MAX_BITMAP_DIMENSION = 512; + private final static int TRANSPORT_UNKNOWN = 0; + private final static String TAG = "RemoteController"; + private final static boolean DEBUG = false; + private final static Object mGenLock = new Object(); + private final static Object mInfoLock = new Object(); + private final RcDisplay mRcd; + private final Context mContext; + private final AudioManager mAudioManager; + private final int mMaxBitmapDimension; + private MetadataEditor mMetadataEditor; + + /** + * Synchronized on mGenLock + */ + private int mClientGenerationIdCurrent = 0; + + /** + * Synchronized on mInfoLock + */ + private boolean mIsRegistered = false; + private PendingIntent mClientPendingIntentCurrent; + private OnClientUpdateListener mOnClientUpdateListener; + private PlaybackInfo mLastPlaybackInfo; + private int mArtworkWidth = -1; + private int mArtworkHeight = -1; + private boolean mEnabled = true; + + /** + * Class constructor. + * @param context the {@link Context}, must be non-null. + * @param updateListener the listener to be called whenever new client information is available, + * must be non-null. + * @throws IllegalArgumentException + */ + public RemoteController(Context context, OnClientUpdateListener updateListener) + throws IllegalArgumentException { + this(context, updateListener, null); + } + + /** + * Class constructor. + * @param context the {@link Context}, must be non-null. + * @param updateListener the listener to be called whenever new client information is available, + * must be non-null. + * @param looper the {@link Looper} on which to run the event loop, + * or null to use the current thread's looper. + * @throws java.lang.IllegalArgumentException + */ + public RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper) + throws IllegalArgumentException { + if (context == null) { + throw new IllegalArgumentException("Invalid null Context"); + } + if (updateListener == null) { + throw new IllegalArgumentException("Invalid null OnClientUpdateListener"); + } + if (looper != null) { + mEventHandler = new EventHandler(this, looper); + } else { + Looper l = Looper.myLooper(); + if (l != null) { + mEventHandler = new EventHandler(this, l); + } else { + throw new IllegalArgumentException("Calling thread not associated with a looper"); + } + } + mOnClientUpdateListener = updateListener; + mContext = context; + mRcd = new RcDisplay(this); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + if (ActivityManager.isLowRamDeviceStatic()) { + mMaxBitmapDimension = MAX_BITMAP_DIMENSION; + } else { + final DisplayMetrics dm = context.getResources().getDisplayMetrics(); + mMaxBitmapDimension = Math.max(dm.widthPixels, dm.heightPixels); + } + } + + + /** + * Interface definition for the callbacks to be invoked whenever media events, metadata + * and playback status are available. + */ + public interface OnClientUpdateListener { + /** + * Called whenever all information, previously received through the other + * methods of the listener, is no longer valid and is about to be refreshed. + * This is typically called whenever a new {@link RemoteControlClient} has been selected + * by the system to have its media information published. + * @param clearing true if there is no selected RemoteControlClient and no information + * is available. + */ + public void onClientChange(boolean clearing); + + /** + * Called whenever the playback state has changed. + * It is called when no information is known about the playback progress in the media and + * the playback speed. + * @param state one of the playback states authorized + * in {@link RemoteControlClient#setPlaybackState(int)}. + */ + public void onClientPlaybackStateUpdate(int state); + /** + * Called whenever the playback state has changed, and playback position + * and speed are known. + * @param state one of the playback states authorized + * in {@link RemoteControlClient#setPlaybackState(int)}. + * @param stateChangeTimeMs the system time at which the state change was reported, + * expressed in ms. Based on {@link android.os.SystemClock#elapsedRealtime()}. + * @param currentPosMs a positive value for the current media playback position expressed + * in ms, a negative value if the position is temporarily unknown. + * @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback, + * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is + * playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}). + */ + public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, + long currentPosMs, float speed); + /** + * Called whenever the transport control flags have changed. + * @param transportControlFlags one of the flags authorized + * in {@link RemoteControlClient#setTransportControlFlags(int)}. + */ + public void onClientTransportControlUpdate(int transportControlFlags); + /** + * Called whenever new metadata is available. + * See the {@link MediaMetadataEditor#putLong(int, long)}, + * {@link MediaMetadataEditor#putString(int, String)}, + * {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and + * {@link MediaMetadataEditor#putObject(int, Object)} methods for the various keys that + * can be queried. + * @param metadataEditor the container of the new metadata. + */ + public void onClientMetadataUpdate(MetadataEditor metadataEditor); + }; + + + /** + * @hide + */ + public String getRemoteControlClientPackageName() { + return mClientPendingIntentCurrent != null ? + mClientPendingIntentCurrent.getCreatorPackage() : null; + } + + /** + * Return the estimated playback position of the current media track or a negative value + * if not available. + * + *

        The value returned is estimated by the current process and may not be perfect. + * The time returned by this method is calculated from the last state change time based + * on the current play position at that time and the last known playback speed. + * An application may call {@link #setSynchronizationMode(int)} to apply + * a synchronization policy that will periodically re-sync the estimated position + * with the RemoteControlClient.

        + * + * @return the current estimated playback position in milliseconds or a negative value + * if not available + * + * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float) + */ + public long getEstimatedMediaPosition() { + if (mLastPlaybackInfo != null) { + if (!RemoteControlClient.playbackPositionShouldMove(mLastPlaybackInfo.mState)) { + return mLastPlaybackInfo.mCurrentPosMs; + } + + // Take the current position at the time of state change and estimate. + final long thenPos = mLastPlaybackInfo.mCurrentPosMs; + if (thenPos < 0) { + return -1; + } + + final long now = SystemClock.elapsedRealtime(); + final long then = mLastPlaybackInfo.mStateChangeTimeMs; + final long sinceThen = now - then; + final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed); + return thenPos + scaledSinceThen; + } + return -1; + } + + + /** + * Send a simulated key event for a media button to be received by the current client. + * To simulate a key press, you must first send a KeyEvent built with + * a {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} + * action. + *

        The key event will be sent to the registered receiver + * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated + * {@link RemoteControlClient}'s metadata and playback state is published (there may be + * none under some circumstances). + * @param keyEvent a {@link KeyEvent} instance whose key code is one of + * {@link KeyEvent#KEYCODE_MUTE}, + * {@link KeyEvent#KEYCODE_HEADSETHOOK}, + * {@link KeyEvent#KEYCODE_MEDIA_PLAY}, + * {@link KeyEvent#KEYCODE_MEDIA_PAUSE}, + * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}, + * {@link KeyEvent#KEYCODE_MEDIA_STOP}, + * {@link KeyEvent#KEYCODE_MEDIA_NEXT}, + * {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}, + * {@link KeyEvent#KEYCODE_MEDIA_REWIND}, + * {@link KeyEvent#KEYCODE_MEDIA_RECORD}, + * {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}, + * {@link KeyEvent#KEYCODE_MEDIA_CLOSE}, + * {@link KeyEvent#KEYCODE_MEDIA_EJECT}, + * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. + * @return true if the event was successfully sent, false otherwise. + * @throws IllegalArgumentException + */ + public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException { + if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) { + throw new IllegalArgumentException("not a media key event"); + } + final PendingIntent pi; + synchronized(mInfoLock) { + if (!mIsRegistered) { + Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); + return false; + } + if (!mEnabled) { + Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController"); + return false; + } + pi = mClientPendingIntentCurrent; + } + if (pi != null) { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); + intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + try { + pi.send(mContext, 0, intent); + } catch (CanceledException e) { + Log.e(TAG, "Error sending intent for media button down: ", e); + return false; + } + } else { + Log.i(TAG, "No-op when sending key click, no receiver right now"); + return false; + } + return true; + } + + + /** + * Sets the new playback position. + * This method can only be called on a registered RemoteController. + * @param timeMs a 0 or positive value for the new playback position, expressed in ms. + * @return true if the command to set the playback position was successfully sent. + * @throws IllegalArgumentException + */ + public boolean seekTo(long timeMs) throws IllegalArgumentException { + if (!mEnabled) { + Log.e(TAG, "Cannot use seekTo() from a disabled RemoteController"); + return false; + } + if (timeMs < 0) { + throw new IllegalArgumentException("illegal negative time value"); + } + final int genId; + synchronized (mGenLock) { + genId = mClientGenerationIdCurrent; + } + mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs); + return true; + } + + + /** + * @hide + * @param wantBitmap + * @param width + * @param height + * @return true if successful + * @throws IllegalArgumentException + */ + public boolean setArtworkConfiguration(boolean wantBitmap, int width, int height) + throws IllegalArgumentException { + synchronized (mInfoLock) { + if (wantBitmap) { + if ((width > 0) && (height > 0)) { + if (width > mMaxBitmapDimension) { width = mMaxBitmapDimension; } + if (height > mMaxBitmapDimension) { height = mMaxBitmapDimension; } + mArtworkWidth = width; + mArtworkHeight = height; + } else { + throw new IllegalArgumentException("Invalid dimensions"); + } + } else { + mArtworkWidth = -1; + mArtworkHeight = -1; + } + if (mIsRegistered) { + mAudioManager.remoteControlDisplayUsesBitmapSize(mRcd, + mArtworkWidth, mArtworkHeight); + } // else new values have been stored, and will be read by AudioManager with + // RemoteController.getArtworkSize() when AudioManager.registerRemoteController() + // is called. + } + return true; + } + + /** + * Set the maximum artwork image dimensions to be received in the metadata. + * No bitmaps will be received unless this has been specified. + * @param width the maximum width in pixels + * @param height the maximum height in pixels + * @return true if the artwork dimension was successfully set. + * @throws IllegalArgumentException + */ + public boolean setArtworkConfiguration(int width, int height) throws IllegalArgumentException { + return setArtworkConfiguration(true, width, height); + } + + /** + * Prevents this RemoteController from receiving artwork images. + * @return true if receiving artwork images was successfully disabled. + */ + public boolean clearArtworkConfiguration() { + return setArtworkConfiguration(false, -1, -1); + } + + + /** + * Default playback position synchronization mode where the RemoteControlClient is not + * asked regularly for its playback position to see if it has drifted from the estimated + * position. + */ + public static final int POSITION_SYNCHRONIZATION_NONE = 0; + + /** + * The playback position synchronization mode where the RemoteControlClient instances which + * expose their playback position to the framework, will be regularly polled to check + * whether any drift has been noticed between their estimated position and the one they report. + * Note that this mode should only ever be used when needing to display very accurate playback + * position, as regularly polling a RemoteControlClient for its position may have an impact + * on battery life (if applicable) when this query will trigger network transactions in the + * case of remote playback. + */ + public static final int POSITION_SYNCHRONIZATION_CHECK = 1; + + /** + * Set the playback position synchronization mode. + * Must be called on a registered RemoteController. + * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK} + * @return true if the synchronization mode was successfully set. + * @throws IllegalArgumentException + */ + public boolean setSynchronizationMode(int sync) throws IllegalArgumentException { + if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) { + throw new IllegalArgumentException("Unknown synchronization mode " + sync); + } + if (!mIsRegistered) { + Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController"); + return false; + } + mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd, + POSITION_SYNCHRONIZATION_CHECK == sync); + return true; + } + + + /** + * Creates a {@link MetadataEditor} for updating metadata values of the editable keys of + * the current {@link RemoteControlClient}. + * This method can only be called on a registered RemoteController. + * @return a new MetadataEditor instance. + */ + public MetadataEditor editMetadata() { + MetadataEditor editor = new MetadataEditor(); + editor.mEditorMetadata = new Bundle(); + editor.mEditorArtwork = null; + editor.mMetadataChanged = true; + editor.mArtworkChanged = true; + editor.mEditableKeys = 0; + return editor; + } + + + /** + * A class to read the metadata published by a {@link RemoteControlClient}, or send a + * {@link RemoteControlClient} new values for keys that can be edited. + */ + public class MetadataEditor extends MediaMetadataEditor { + /** + * @hide + */ + protected MetadataEditor() { } + + /** + * @hide + */ + protected MetadataEditor(Bundle metadata, long editableKeys) { + mEditorMetadata = metadata; + mEditableKeys = editableKeys; + + mEditorArtwork = (Bitmap) metadata.getParcelable( + String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)); + if (mEditorArtwork != null) { + cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); + } + + mMetadataChanged = true; + mArtworkChanged = true; + mApplied = false; + } + + private void cleanupBitmapFromBundle(int key) { + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) == METADATA_TYPE_BITMAP) { + mEditorMetadata.remove(String.valueOf(key)); + } + } + + /** + * Applies all of the metadata changes that have been set since the MediaMetadataEditor + * instance was created with {@link RemoteController#editMetadata()} + * or since {@link #clear()} was called. + */ + public synchronized void apply() { + // "applying" a metadata bundle in RemoteController is only for sending edited + // key values back to the RemoteControlClient, so here we only care about the only + // editable key we support: RATING_KEY_BY_USER + if (!mMetadataChanged) { + return; + } + final int genId; + synchronized(mGenLock) { + genId = mClientGenerationIdCurrent; + } + synchronized(mInfoLock) { + if (mEditorMetadata.containsKey( + String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) { + Rating rating = (Rating) getObject( + MediaMetadataEditor.RATING_KEY_BY_USER, null); + mAudioManager.updateRemoteControlClientMetadata(genId, + MediaMetadataEditor.RATING_KEY_BY_USER, + rating); + } else { + Log.e(TAG, "no metadata to apply"); + } + // NOT setting mApplied to true as this type of MetadataEditor will be applied + // multiple times, whenever the user of a RemoteController needs to change the + // metadata (e.g. user changes the rating of a song more than once during playback) + mApplied = false; + } + } + + } + + + //================================================== + // Implementation of IRemoteControlDisplay interface + private static class RcDisplay extends IRemoteControlDisplay.Stub { + private final WeakReference mController; + + RcDisplay(RemoteController rc) { + mController = new WeakReference(rc); + } + + public void setCurrentClientId(int genId, PendingIntent clientMediaIntent, + boolean clearing) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + boolean isNew = false; + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + rc.mClientGenerationIdCurrent = genId; + isNew = true; + } + } + if (clientMediaIntent != null) { + sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE, + genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/); + } + if (isNew || clearing) { + sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, + genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/); + } + } + + public void setEnabled(boolean enabled) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE, + enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/); + } + + public void setPlaybackState(int genId, int state, + long stateChangeTimeMs, long currentPosMs, float speed) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "> new playback state: genId="+genId + + " state="+ state + + " changeTime="+ stateChangeTimeMs + + " pos=" + currentPosMs + + "ms speed=" + speed); + } + + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + final PlaybackInfo playbackInfo = + new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed); + sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, + genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/); + + } + + public void setTransportControlInfo(int genId, int transportControlFlags, + int posCapabilities) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, + genId /*arg1*/, transportControlFlags /*arg2*/, + null /*obj*/, 0 /*delay*/); + } + + public void setMetadata(int genId, Bundle metadata) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); } + if (metadata == null) { + return; + } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2*/, + metadata /*obj*/, 0 /*delay*/); + } + + public void setArtwork(int genId, Bitmap artwork) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + Bundle metadata = new Bundle(1); + metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork); + sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2*/, + metadata /*obj*/, 0 /*delay*/); + } + + public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); } + if ((metadata == null) && (artwork == null)) { + return; + } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + if (metadata == null) { + metadata = new Bundle(1); + } + if (artwork != null) { + metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), + artwork); + } + sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2*/, + metadata /*obj*/, 0 /*delay*/); + } + } + + //================================================== + // Event handling + private final EventHandler mEventHandler; + private final static int MSG_NEW_PENDING_INTENT = 0; + private final static int MSG_NEW_PLAYBACK_INFO = 1; + private final static int MSG_NEW_TRANSPORT_INFO = 2; + private final static int MSG_NEW_METADATA = 3; // msg always has non-null obj parameter + private final static int MSG_CLIENT_CHANGE = 4; + private final static int MSG_DISPLAY_ENABLE = 5; + + private class EventHandler extends Handler { + + public EventHandler(RemoteController rc, Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_NEW_PENDING_INTENT: + onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj); + break; + case MSG_NEW_PLAYBACK_INFO: + onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj); + break; + case MSG_NEW_TRANSPORT_INFO: + onNewTransportInfo(msg.arg1, msg.arg2); + break; + case MSG_NEW_METADATA: + onNewMetadata(msg.arg1, (Bundle)msg.obj); + break; + case MSG_CLIENT_CHANGE: + onClientChange(msg.arg1, msg.arg2 == 1); + break; + case MSG_DISPLAY_ENABLE: + onDisplayEnable(msg.arg1 == 1); + break; + default: + Log.e(TAG, "unknown event " + msg.what); + } + } + } + + /** If the msg is already queued, replace it with this one. */ + private static final int SENDMSG_REPLACE = 0; + /** If the msg is already queued, ignore this one and leave the old. */ + private static final int SENDMSG_NOOP = 1; + /** If the msg is already queued, queue this one and leave the old. */ + private static final int SENDMSG_QUEUE = 2; + + private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, + int arg1, int arg2, Object obj, int delayMs) { + if (handler == null) { + Log.e(TAG, "null event handler, will not deliver message " + msg); + return; + } + if (existingMsgPolicy == SENDMSG_REPLACE) { + handler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { + return; + } + handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs); + } + + private void onNewPendingIntent(int genId, PendingIntent pi) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + synchronized(mInfoLock) { + mClientPendingIntentCurrent = pi; + } + } + + private void onNewPlaybackInfo(int genId, PlaybackInfo pi) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + synchronized(mInfoLock) { + l = this.mOnClientUpdateListener; + mLastPlaybackInfo = pi; + } + if (l != null) { + if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { + l.onClientPlaybackStateUpdate(pi.mState); + } else { + l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs, + pi.mSpeed); + } + } + } + + private void onNewTransportInfo(int genId, int transportControlFlags) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + synchronized(mInfoLock) { + l = mOnClientUpdateListener; + } + if (l != null) { + l.onClientTransportControlUpdate(transportControlFlags); + } + } + + /** + * @param genId + * @param metadata guaranteed to be always non-null + */ + private void onNewMetadata(int genId, Bundle metadata) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + final MetadataEditor metadataEditor; + // prepare the received Bundle to be used inside a MetadataEditor + final long editableKeys = metadata.getLong( + String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0); + if (editableKeys != 0) { + metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK)); + } + synchronized(mInfoLock) { + l = mOnClientUpdateListener; + if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) { + if (mMetadataEditor.mEditorMetadata != metadata) { + // existing metadata, merge existing and new + mMetadataEditor.mEditorMetadata.putAll(metadata); + } + + mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, + (Bitmap)metadata.getParcelable( + String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK))); + mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); + } else { + mMetadataEditor = new MetadataEditor(metadata, editableKeys); + } + metadataEditor = mMetadataEditor; + } + if (l != null) { + l.onClientMetadataUpdate(metadataEditor); + } + } + + private void onClientChange(int genId, boolean clearing) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + synchronized(mInfoLock) { + l = mOnClientUpdateListener; + } + if (l != null) { + l.onClientChange(clearing); + } + } + + private void onDisplayEnable(boolean enabled) { + final OnClientUpdateListener l; + synchronized(mInfoLock) { + mEnabled = enabled; + l = this.mOnClientUpdateListener; + } + if (!enabled) { + // when disabling, reset all info sent to the user + final int genId; + synchronized (mGenLock) { + genId = mClientGenerationIdCurrent; + } + // send "stopped" state, happened "now", playback position is 0, speed 0.0f + final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED, + SystemClock.elapsedRealtime() /*stateChangeTimeMs*/, + 0 /*currentPosMs*/, 0.0f /*speed*/); + sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, + genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/); + // send "blank" transport control info: no controls are supported + sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, + genId /*arg1*/, 0 /*arg2, no flags*/, + null /*obj, ignored*/, 0 /*delay*/); + // send dummy metadata with empty string for title and artist, duration of 0 + Bundle metadata = new Bundle(3); + metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), ""); + metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), ""); + metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0); + sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/); + } + } + + //================================================== + private static class PlaybackInfo { + int mState; + long mStateChangeTimeMs; + long mCurrentPosMs; + float mSpeed; + + PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) { + mState = state; + mStateChangeTimeMs = stateChangeTimeMs; + mCurrentPosMs = currentPosMs; + mSpeed = speed; + } + } + + /** + * @hide + * Used by AudioManager to mark this instance as registered. + * @param registered + */ + void setIsRegistered(boolean registered) { + synchronized (mInfoLock) { + mIsRegistered = registered; + } + } + + /** + * @hide + * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl + * @return + */ + RcDisplay getRcDisplay() { + return mRcd; + } + + /** + * @hide + * Used by AudioManager to read the current artwork dimension + * @return array containing width (index 0) and height (index 1) of currently set artwork size + */ + int[] getArtworkSize() { + synchronized (mInfoLock) { + int[] size = { mArtworkWidth, mArtworkHeight }; + return size; + } + } + + /** + * @hide + * Used by AudioManager to access user listener receiving the client update notifications + * @return + */ + OnClientUpdateListener getUpdateListener() { + return mOnClientUpdateListener; + } +} diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java index b463d267f685f56b1df2d18284b93b2b5d16b804..7afce1aad7fc4fc27b156421a7570fd3c9426dd7 100644 --- a/media/java/android/media/RemoteDisplay.java +++ b/media/java/android/media/RemoteDisplay.java @@ -42,6 +42,8 @@ public final class RemoteDisplay { private native int nativeListen(String iface); private native void nativeDispose(int ptr); + private native void nativePause(int ptr); + private native void nativeResume(int ptr); private RemoteDisplay(Listener listener, Handler handler) { mListener = listener; @@ -87,6 +89,14 @@ public final class RemoteDisplay { dispose(false); } + public void pause() { + nativePause(mPtr); + } + + public void resume() { + nativeResume(mPtr); + } + private void dispose(boolean finalized) { if (mPtr != 0) { if (mGuard != null) { @@ -113,11 +123,11 @@ public final class RemoteDisplay { // Called from native. private void notifyDisplayConnected(final Surface surface, - final int width, final int height, final int flags) { + final int width, final int height, final int flags, final int session) { mHandler.post(new Runnable() { @Override public void run() { - mListener.onDisplayConnected(surface, width, height, flags); + mListener.onDisplayConnected(surface, width, height, flags, session); } }); } @@ -146,7 +156,8 @@ public final class RemoteDisplay { * Listener invoked when the remote display connection changes state. */ public interface Listener { - void onDisplayConnected(Surface surface, int width, int height, int flags); + void onDisplayConnected(Surface surface, + int width, int height, int flags, int session); void onDisplayDisconnected(); void onDisplayError(int error); } diff --git a/media/java/android/media/ResourceBusyException.java b/media/java/android/media/ResourceBusyException.java new file mode 100644 index 0000000000000000000000000000000000000000..a5abe2119e0068ce0bb0c5eb950aba194a00e6ac --- /dev/null +++ b/media/java/android/media/ResourceBusyException.java @@ -0,0 +1,27 @@ +/* + * 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.media; + +/** + * Exception thrown when an operation on a MediaDrm object is attempted + * and hardware resources are not available, due to being in use. + */ +public final class ResourceBusyException extends MediaDrmException { + public ResourceBusyException(String detailMessage) { + super(detailMessage); + } +} diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index ebbfad96d94d2345a5b7e5e29e0859ee48dfbf82..1283e9b5b1e8c74188119783d0b2769372af3af1 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -24,7 +24,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.RemoteException; -import android.provider.DrmStore; import android.provider.MediaStore; import android.provider.Settings; import android.util.Log; @@ -50,12 +49,6 @@ public class Ringtone { MediaStore.Audio.Media.TITLE }; - private static final String[] DRM_COLUMNS = new String[] { - DrmStore.Audio._ID, - DrmStore.Audio.DATA, - DrmStore.Audio.TITLE - }; - private final Context mContext; private final AudioManager mAudioManager; private final boolean mAllowRemote; @@ -101,8 +94,8 @@ public class Ringtone { } /** - * Returns a human-presentable title for ringtone. Looks in media and DRM - * content providers. If not in either, uses the filename + * Returns a human-presentable title for ringtone. Looks in media + * content provider. If not in either, uses the filename * * @param context A context used for querying. */ @@ -131,9 +124,7 @@ public class Ringtone { } } else { try { - if (DrmStore.AUTHORITY.equals(authority)) { - cursor = res.query(uri, DRM_COLUMNS, null, null, null); - } else if (MediaStore.AUTHORITY.equals(authority)) { + if (MediaStore.AUTHORITY.equals(authority)) { cursor = res.query(uri, MEDIA_COLUMNS, null, null, null); } } catch (SecurityException e) { @@ -289,7 +280,7 @@ public class Ringtone { private boolean playFallbackRingtone() { if (mAudioManager.getStreamVolume(mStreamType) != 0) { int ringtoneType = RingtoneManager.getDefaultType(mUri); - if (ringtoneType != -1 && + if (ringtoneType == -1 || RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) != null) { // Default ringtone, try fallback ringtone. try { @@ -318,6 +309,8 @@ public class Ringtone { } catch (NotFoundException nfe) { Log.e(TAG, "Fallback ringtone does not exist"); } + } else { + Log.w(TAG, "not playing fallback for " + mUri); } } return false; diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 5e18bfad0c1b81f0218d531e27cb0ae1a428adb8..8e4004b61db78c527e60184f72ed91f8bd653345 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -27,7 +27,6 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Environment; -import android.provider.DrmStore; import android.provider.MediaStore; import android.provider.Settings; import android.provider.Settings.System; @@ -85,7 +84,6 @@ public class RingtoneManager { * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}, * {@link #EXTRA_RINGTONE_SHOW_SILENT}, {@link #EXTRA_RINGTONE_TYPE}, * {@link #EXTRA_RINGTONE_DEFAULT_URI}, {@link #EXTRA_RINGTONE_TITLE}, - * {@link #EXTRA_RINGTONE_INCLUDE_DRM}. *

        * Output: {@link #EXTRA_RINGTONE_PICKED_URI}. */ @@ -113,7 +111,9 @@ public class RingtoneManager { /** * Given to the ringtone picker as a boolean. Whether to include DRM ringtones. + * @deprecated DRM ringtones are no longer supported */ + @Deprecated public static final String EXTRA_RINGTONE_INCLUDE_DRM = "android.intent.extra.ringtone.INCLUDE_DRM"; @@ -183,12 +183,6 @@ public class RingtoneManager { MediaStore.Audio.Media.TITLE_KEY }; - private static final String[] DRM_COLUMNS = new String[] { - DrmStore.Audio._ID, DrmStore.Audio.TITLE, - "\"" + DrmStore.Audio.CONTENT_URI + "\"", - DrmStore.Audio.TITLE + " AS " + MediaStore.Audio.Media.TITLE_KEY - }; - private static final String[] MEDIA_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"", @@ -228,8 +222,6 @@ public class RingtoneManager { private boolean mStopPreviousRingtone = true; private Ringtone mPreviousRingtone; - - private boolean mIncludeDrm; /** * Constructs a RingtoneManager. This constructor is recommended as its @@ -328,18 +320,26 @@ public class RingtoneManager { * * @return Whether DRM ringtones will be included. * @see #setIncludeDrm(boolean) + * Obsolete - always returns false + * @deprecated DRM ringtones are no longer supported */ + @Deprecated public boolean getIncludeDrm() { - return mIncludeDrm; + return false; } /** * Sets whether to include DRM ringtones. * * @param includeDrm Whether to include DRM ringtones. + * Obsolete - no longer has any effect + * @deprecated DRM ringtones are no longer supported */ + @Deprecated public void setIncludeDrm(boolean includeDrm) { - mIncludeDrm = includeDrm; + if (includeDrm) { + Log.w(TAG, "setIncludeDrm no longer supported"); + } } /** @@ -363,10 +363,9 @@ public class RingtoneManager { } final Cursor internalCursor = getInternalRingtones(); - final Cursor drmCursor = mIncludeDrm ? getDrmRingtones() : null; final Cursor mediaCursor = getMediaRingtones(); - return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor }, + return mCursor = new SortCursor(new Cursor[] { internalCursor, mediaCursor }, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); } @@ -462,10 +461,6 @@ public class RingtoneManager { uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); } - if (uri == null) { - uri = getValidRingtoneUriFromCursorAndClose(context, rm.getDrmRingtones()); - } - return uri; } @@ -487,16 +482,9 @@ public class RingtoneManager { private Cursor getInternalRingtones() { return query( MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns, mIncludeDrm), + constructBooleanTrueWhereClause(mFilterColumns), null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); } - - private Cursor getDrmRingtones() { - // DRM store does not have any columns to use for filtering - return query( - DrmStore.Audio.CONTENT_URI, DRM_COLUMNS, - null, null, DrmStore.Audio.TITLE); - } private Cursor getMediaRingtones() { // Get the external media cursor. First check to see if it is mounted. @@ -506,7 +494,7 @@ public class RingtoneManager { status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) ? query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns, mIncludeDrm), null, + constructBooleanTrueWhereClause(mFilterColumns), null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER) : null; } @@ -536,7 +524,7 @@ public class RingtoneManager { * @param columns The columns that must be true. * @return The where clause. */ - private static String constructBooleanTrueWhereClause(List columns, boolean includeDrm) { + private static String constructBooleanTrueWhereClause(List columns) { if (columns == null) return null; @@ -554,15 +542,6 @@ public class RingtoneManager { sb.append(")"); - if (!includeDrm) { - // If not DRM files should be shown, the where clause - // will be something like "(is_notification=1) and is_drm=0" - sb.append(" and "); - sb.append(MediaStore.MediaColumns.IS_DRM); - sb.append("=0"); - } - - return sb.toString(); } diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java index 587af47bb030334fc07eada6b0a6575d0fb838c2..06af5de693b3ddba27fe1454c291ca3f40c81508 100644 --- a/media/java/android/media/SoundPool.java +++ b/media/java/android/media/SoundPool.java @@ -16,19 +16,21 @@ package android.media; -import android.util.AndroidRuntimeException; -import android.util.Log; import java.io.File; import java.io.FileDescriptor; -import android.os.ParcelFileDescriptor; +import java.io.IOException; import java.lang.ref.WeakReference; + import android.content.Context; import android.content.res.AssetFileDescriptor; -import java.io.IOException; - import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.SystemProperties; +import android.util.AndroidRuntimeException; +import android.util.Log; + /** * The SoundPool class manages and plays audio resources for applications. @@ -102,24 +104,8 @@ import android.os.Message; * another level, a new SoundPool is created, sounds are loaded, and play * resumes.

        */ -public class SoundPool -{ - static { System.loadLibrary("soundpool"); } - - private final static String TAG = "SoundPool"; - private final static boolean DEBUG = false; - - private int mNativeContext; // accessed by native methods - - private EventHandler mEventHandler; - private OnLoadCompleteListener mOnLoadCompleteListener; - - private final Object mLock; - - // SoundPool messages - // - // must match SoundPool.h - private static final int SAMPLE_LOADED = 1; +public class SoundPool { + private final SoundPoolDelegate mImpl; /** * Constructor. Constructs a SoundPool object with the following @@ -135,12 +121,11 @@ public class SoundPool * @return a SoundPool object, or null if creation failed */ public SoundPool(int maxStreams, int streamType, int srcQuality) { - - // do native setup - if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) { - throw new RuntimeException("Native setup failed"); + if (SystemProperties.getBoolean("config.disable_media", false)) { + mImpl = new SoundPoolStub(); + } else { + mImpl = new SoundPoolImpl(this, maxStreams, streamType, srcQuality); } - mLock = new Object(); } /** @@ -151,25 +136,8 @@ public class SoundPool * a value of 1 for future compatibility. * @return a sound ID. This value can be used to play or unload the sound. */ - public int load(String path, int priority) - { - // pass network streams to player - if (path.startsWith("http:")) - return _load(path, priority); - - // try local path - int id = 0; - try { - File f = new File(path); - ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); - if (fd != null) { - id = _load(fd.getFileDescriptor(), 0, f.length(), priority); - fd.close(); - } - } catch (java.io.IOException e) { - Log.e(TAG, "error loading " + path); - } - return id; + public int load(String path, int priority) { + return mImpl.load(path, priority); } /** @@ -188,17 +156,7 @@ public class SoundPool * @return a sound ID. This value can be used to play or unload the sound. */ public int load(Context context, int resId, int priority) { - AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); - int id = 0; - if (afd != null) { - id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority); - try { - afd.close(); - } catch (java.io.IOException ex) { - //Log.d(TAG, "close failed:", ex); - } - } - return id; + return mImpl.load(context, resId, priority); } /** @@ -210,15 +168,7 @@ public class SoundPool * @return a sound ID. This value can be used to play or unload the sound. */ public int load(AssetFileDescriptor afd, int priority) { - if (afd != null) { - long len = afd.getLength(); - if (len < 0) { - throw new AndroidRuntimeException("no length for fd"); - } - return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority); - } else { - return 0; - } + return mImpl.load(afd, priority); } /** @@ -236,13 +186,9 @@ public class SoundPool * @return a sound ID. This value can be used to play or unload the sound. */ public int load(FileDescriptor fd, long offset, long length, int priority) { - return _load(fd, offset, length, priority); + return mImpl.load(fd, offset, length, priority); } - private native final int _load(String uri, int priority); - - private native final int _load(FileDescriptor fd, long offset, long length, int priority); - /** * Unload a sound from a sound ID. * @@ -253,7 +199,9 @@ public class SoundPool * @param soundID a soundID returned by the load() function * @return true if just unloaded, false if previously unloaded */ - public native final boolean unload(int soundID); + public final boolean unload(int soundID) { + return mImpl.unload(soundID); + } /** * Play a sound from a sound ID. @@ -279,8 +227,11 @@ public class SoundPool * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0) * @return non-zero streamID if successful, zero if failed */ - public native final int play(int soundID, float leftVolume, float rightVolume, - int priority, int loop, float rate); + public final int play(int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate) { + return mImpl.play( + soundID, leftVolume, rightVolume, priority, loop, rate); + } /** * Pause a playback stream. @@ -293,7 +244,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void pause(int streamID); + public final void pause(int streamID) { + mImpl.pause(streamID); + } /** * Resume a playback stream. @@ -305,7 +258,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void resume(int streamID); + public final void resume(int streamID) { + mImpl.resume(streamID); + } /** * Pause all active streams. @@ -315,7 +270,9 @@ public class SoundPool * are playing. It also sets a flag so that any streams that * are playing can be resumed by calling autoResume(). */ - public native final void autoPause(); + public final void autoPause() { + mImpl.autoPause(); + } /** * Resume all previously active streams. @@ -323,7 +280,9 @@ public class SoundPool * Automatically resumes all streams that were paused in previous * calls to autoPause(). */ - public native final void autoResume(); + public final void autoResume() { + mImpl.autoResume(); + } /** * Stop a playback stream. @@ -336,7 +295,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void stop(int streamID); + public final void stop(int streamID) { + mImpl.stop(streamID); + } /** * Set stream volume. @@ -350,8 +311,10 @@ public class SoundPool * @param leftVolume left volume value (range = 0.0 to 1.0) * @param rightVolume right volume value (range = 0.0 to 1.0) */ - public native final void setVolume(int streamID, - float leftVolume, float rightVolume); + public final void setVolume(int streamID, + float leftVolume, float rightVolume) { + mImpl.setVolume(streamID, leftVolume, rightVolume); + } /** * Similar, except set volume of all channels to same value. @@ -371,7 +334,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void setPriority(int streamID, int priority); + public final void setPriority(int streamID, int priority) { + mImpl.setPriority(streamID, priority); + } /** * Set loop mode. @@ -384,7 +349,9 @@ public class SoundPool * @param streamID a streamID returned by the play() function * @param loop loop mode (0 = no loop, -1 = loop forever) */ - public native final void setLoop(int streamID, int loop); + public final void setLoop(int streamID, int loop) { + mImpl.setLoop(streamID, loop); + } /** * Change playback rate. @@ -398,19 +365,16 @@ public class SoundPool * @param streamID a streamID returned by the play() function * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0) */ - public native final void setRate(int streamID, float rate); + public final void setRate(int streamID, float rate) { + mImpl.setRate(streamID, rate); + } - /** - * Interface definition for a callback to be invoked when all the - * sounds are loaded. - */ - public interface OnLoadCompleteListener - { + public interface OnLoadCompleteListener { /** * Called when a sound has completed loading. * * @param soundPool SoundPool object from the load() method - * @param soundPool the sample ID of the sound loaded. + * @param sampleId the sample ID of the sound loaded. * @param status the status of the load operation (0 = success) */ public void onLoadComplete(SoundPool soundPool, int sampleId, int status); @@ -419,76 +383,297 @@ public class SoundPool /** * Sets the callback hook for the OnLoadCompleteListener. */ - public void setOnLoadCompleteListener(OnLoadCompleteListener listener) - { - synchronized(mLock) { - if (listener != null) { - // setup message handler - Looper looper; - if ((looper = Looper.myLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else if ((looper = Looper.getMainLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else { - mEventHandler = null; + public void setOnLoadCompleteListener(OnLoadCompleteListener listener) { + mImpl.setOnLoadCompleteListener(listener); + } + + /** + * Release the SoundPool resources. + * + * Release all memory and native resources used by the SoundPool + * object. The SoundPool can no longer be used and the reference + * should be set to null. + */ + public final void release() { + mImpl.release(); + } + + /** + * Interface for SoundPool implementations. + * SoundPool is statically referenced and unconditionally called from all + * over the framework, so we can't simply omit the class or make it throw + * runtime exceptions, as doing so would break the framework. Instead we + * now select either a real or no-op impl object based on whether media is + * enabled. + * + * @hide + */ + /* package */ interface SoundPoolDelegate { + public int load(String path, int priority); + public int load(Context context, int resId, int priority); + public int load(AssetFileDescriptor afd, int priority); + public int load( + FileDescriptor fd, long offset, long length, int priority); + public boolean unload(int soundID); + public int play( + int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate); + public void pause(int streamID); + public void resume(int streamID); + public void autoPause(); + public void autoResume(); + public void stop(int streamID); + public void setVolume(int streamID, float leftVolume, float rightVolume); + public void setVolume(int streamID, float volume); + public void setPriority(int streamID, int priority); + public void setLoop(int streamID, int loop); + public void setRate(int streamID, float rate); + public void setOnLoadCompleteListener(OnLoadCompleteListener listener); + public void release(); + } + + + /** + * Real implementation of the delegate interface. This was formerly the + * body of SoundPool itself. + */ + /* package */ static class SoundPoolImpl implements SoundPoolDelegate { + static { System.loadLibrary("soundpool"); } + + private final static String TAG = "SoundPool"; + private final static boolean DEBUG = false; + + private int mNativeContext; // accessed by native methods + + private EventHandler mEventHandler; + private SoundPool.OnLoadCompleteListener mOnLoadCompleteListener; + private SoundPool mProxy; + + private final Object mLock; + + // SoundPool messages + // + // must match SoundPool.h + private static final int SAMPLE_LOADED = 1; + + public SoundPoolImpl(SoundPool proxy, int maxStreams, int streamType, int srcQuality) { + + // do native setup + if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) { + throw new RuntimeException("Native setup failed"); + } + mLock = new Object(); + mProxy = proxy; + } + + public int load(String path, int priority) + { + // pass network streams to player + if (path.startsWith("http:")) + return _load(path, priority); + + // try local path + int id = 0; + try { + File f = new File(path); + ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); + if (fd != null) { + id = _load(fd.getFileDescriptor(), 0, f.length(), priority); + fd.close(); } + } catch (java.io.IOException e) { + Log.e(TAG, "error loading " + path); + } + return id; + } + + public int load(Context context, int resId, int priority) { + AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); + int id = 0; + if (afd != null) { + id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority); + try { + afd.close(); + } catch (java.io.IOException ex) { + //Log.d(TAG, "close failed:", ex); + } + } + return id; + } + + public int load(AssetFileDescriptor afd, int priority) { + if (afd != null) { + long len = afd.getLength(); + if (len < 0) { + throw new AndroidRuntimeException("no length for fd"); + } + return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority); } else { - mEventHandler = null; + return 0; } - mOnLoadCompleteListener = listener; } - } - private class EventHandler extends Handler - { - private SoundPool mSoundPool; + public int load(FileDescriptor fd, long offset, long length, int priority) { + return _load(fd, offset, length, priority); + } + + private native final int _load(String uri, int priority); + + private native final int _load(FileDescriptor fd, long offset, long length, int priority); + + public native final boolean unload(int soundID); + + public native final int play(int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate); + + public native final void pause(int streamID); + + public native final void resume(int streamID); + + public native final void autoPause(); + + public native final void autoResume(); + + public native final void stop(int streamID); - public EventHandler(SoundPool soundPool, Looper looper) { - super(looper); - mSoundPool = soundPool; + public native final void setVolume(int streamID, + float leftVolume, float rightVolume); + + public void setVolume(int streamID, float volume) { + setVolume(streamID, volume, volume); } - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case SAMPLE_LOADED: - if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded"); - synchronized(mLock) { - if (mOnLoadCompleteListener != null) { - mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2); + public native final void setPriority(int streamID, int priority); + + public native final void setLoop(int streamID, int loop); + + public native final void setRate(int streamID, float rate); + + public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) + { + synchronized(mLock) { + if (listener != null) { + // setup message handler + Looper looper; + if ((looper = Looper.myLooper()) != null) { + mEventHandler = new EventHandler(mProxy, looper); + } else if ((looper = Looper.getMainLooper()) != null) { + mEventHandler = new EventHandler(mProxy, looper); + } else { + mEventHandler = null; } + } else { + mEventHandler = null; } - break; - default: - Log.e(TAG, "Unknown message type " + msg.what); - return; + mOnLoadCompleteListener = listener; } } - } - // post event from native code to message handler - private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj) - { - SoundPool soundPool = (SoundPool)((WeakReference)weakRef).get(); - if (soundPool == null) - return; + private class EventHandler extends Handler + { + private SoundPool mSoundPool; + + public EventHandler(SoundPool soundPool, Looper looper) { + super(looper); + mSoundPool = soundPool; + } - if (soundPool.mEventHandler != null) { - Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj); - soundPool.mEventHandler.sendMessage(m); + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case SAMPLE_LOADED: + if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded"); + synchronized(mLock) { + if (mOnLoadCompleteListener != null) { + mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2); + } + } + break; + default: + Log.e(TAG, "Unknown message type " + msg.what); + return; + } + } } + + // post event from native code to message handler + private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj) + { + SoundPoolImpl soundPoolImpl = (SoundPoolImpl)((WeakReference)weakRef).get(); + if (soundPoolImpl == null) + return; + + if (soundPoolImpl.mEventHandler != null) { + Message m = soundPoolImpl.mEventHandler.obtainMessage(msg, arg1, arg2, obj); + soundPoolImpl.mEventHandler.sendMessage(m); + } + } + + public native final void release(); + + private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality); + + protected void finalize() { release(); } } /** - * Release the SoundPool resources. - * - * Release all memory and native resources used by the SoundPool - * object. The SoundPool can no longer be used and the reference - * should be set to null. + * No-op implementation of SoundPool. + * Used when media is disabled by the system. + * @hide */ - public native final void release(); + /* package */ static class SoundPoolStub implements SoundPoolDelegate { + public SoundPoolStub() { } + + public int load(String path, int priority) { + return 0; + } + + public int load(Context context, int resId, int priority) { + return 0; + } + + public int load(AssetFileDescriptor afd, int priority) { + return 0; + } + + public int load(FileDescriptor fd, long offset, long length, int priority) { + return 0; + } + + public final boolean unload(int soundID) { + return true; + } + + public final int play(int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate) { + return 0; + } + + public final void pause(int streamID) { } + + public final void resume(int streamID) { } + + public final void autoPause() { } + + public final void autoResume() { } - private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality); + public final void stop(int streamID) { } - protected void finalize() { release(); } + public final void setVolume(int streamID, + float leftVolume, float rightVolume) { } + + public void setVolume(int streamID, float volume) { + } + + public final void setPriority(int streamID, int priority) { } + + public final void setLoop(int streamID, int loop) { } + + public final void setRate(int streamID, float rate) { } + + public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) { + } + + public final void release() { } + } } diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java new file mode 100644 index 0000000000000000000000000000000000000000..13205bc26f54c1d32b39d4905a894f0dde7c994a --- /dev/null +++ b/media/java/android/media/SubtitleController.java @@ -0,0 +1,492 @@ +/* + * 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.media; + +import java.util.Locale; +import java.util.Vector; + +import android.content.Context; +import android.media.SubtitleTrack.RenderingWidget; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.accessibility.CaptioningManager; + +/** + * The subtitle controller provides the architecture to display subtitles for a + * media source. It allows specifying which tracks to display, on which anchor + * to display them, and also allows adding external, out-of-band subtitle tracks. + * + * @hide + */ +public class SubtitleController { + private MediaTimeProvider mTimeProvider; + private Vector mRenderers; + private Vector mTracks; + private SubtitleTrack mSelectedTrack; + private boolean mShowing; + private CaptioningManager mCaptioningManager; + private Handler mHandler; + + private static final int WHAT_SHOW = 1; + private static final int WHAT_HIDE = 2; + private static final int WHAT_SELECT_TRACK = 3; + private static final int WHAT_SELECT_DEFAULT_TRACK = 4; + + private final Handler.Callback mCallback = new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case WHAT_SHOW: + doShow(); + return true; + case WHAT_HIDE: + doHide(); + return true; + case WHAT_SELECT_TRACK: + doSelectTrack((SubtitleTrack)msg.obj); + return true; + case WHAT_SELECT_DEFAULT_TRACK: + doSelectDefaultTrack(); + return true; + default: + return false; + } + } + }; + + private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener = + new CaptioningManager.CaptioningChangeListener() { + /** @hide */ + @Override + public void onEnabledChanged(boolean enabled) { + selectDefaultTrack(); + } + + /** @hide */ + @Override + public void onLocaleChanged(Locale locale) { + selectDefaultTrack(); + } + }; + + /** + * Creates a subtitle controller for a media playback object that implements + * the MediaTimeProvider interface. + * + * @param timeProvider + */ + public SubtitleController( + Context context, + MediaTimeProvider timeProvider, + Listener listener) { + mTimeProvider = timeProvider; + mListener = listener; + + mRenderers = new Vector(); + mShowing = false; + mTracks = new Vector(); + mCaptioningManager = + (CaptioningManager)context.getSystemService(Context.CAPTIONING_SERVICE); + } + + @Override + protected void finalize() throws Throwable { + mCaptioningManager.removeCaptioningChangeListener( + mCaptioningChangeListener); + super.finalize(); + } + + /** + * @return the available subtitle tracks for this media. These include + * the tracks found by {@link MediaPlayer} as well as any tracks added + * manually via {@link #addTrack}. + */ + public SubtitleTrack[] getTracks() { + synchronized(mTracks) { + SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()]; + mTracks.toArray(tracks); + return tracks; + } + } + + /** + * @return the currently selected subtitle track + */ + public SubtitleTrack getSelectedTrack() { + return mSelectedTrack; + } + + private RenderingWidget getRenderingWidget() { + if (mSelectedTrack == null) { + return null; + } + return mSelectedTrack.getRenderingWidget(); + } + + /** + * Selects a subtitle track. As a result, this track will receive + * in-band data from the {@link MediaPlayer}. However, this does + * not change the subtitle visibility. + * + * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper} + * + * @param track The subtitle track to select. This must be one of the + * tracks in {@link #getTracks}. + * @return true if the track was successfully selected. + */ + public boolean selectTrack(SubtitleTrack track) { + if (track != null && !mTracks.contains(track)) { + return false; + } + + processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track)); + return true; + } + + private void doSelectTrack(SubtitleTrack track) { + mTrackIsExplicit = true; + if (mSelectedTrack == track) { + return; + } + + if (mSelectedTrack != null) { + mSelectedTrack.hide(); + mSelectedTrack.setTimeProvider(null); + } + + mSelectedTrack = track; + if (mAnchor != null) { + mAnchor.setSubtitleWidget(getRenderingWidget()); + } + + if (mSelectedTrack != null) { + mSelectedTrack.setTimeProvider(mTimeProvider); + mSelectedTrack.show(); + } + + if (mListener != null) { + mListener.onSubtitleTrackSelected(track); + } + } + + /** + * @return the default subtitle track based on system preferences, or null, + * if no such track exists in this manager. + * + * Supports HLS-flags: AUTOSELECT, FORCED & DEFAULT. + * + * 1. If captioning is disabled, only consider FORCED tracks. Otherwise, + * consider all tracks, but prefer non-FORCED ones. + * 2. If user selected "Default" caption language: + * a. If there is a considered track with DEFAULT=yes, returns that track + * (favor the first one in the current language if there are more than + * one default tracks, or the first in general if none of them are in + * the current language). + * b. Otherwise, if there is a track with AUTOSELECT=yes in the current + * language, return that one. + * c. If there are no default tracks, and no autoselectable tracks in the + * current language, return null. + * 3. If there is a track with the caption language, select that one. Prefer + * the one with AUTOSELECT=no. + * + * The default values for these flags are DEFAULT=no, AUTOSELECT=yes + * and FORCED=no. + */ + public SubtitleTrack getDefaultTrack() { + SubtitleTrack bestTrack = null; + int bestScore = -1; + + Locale selectedLocale = mCaptioningManager.getLocale(); + Locale locale = selectedLocale; + if (locale == null) { + locale = Locale.getDefault(); + } + boolean selectForced = !mCaptioningManager.isEnabled(); + + synchronized(mTracks) { + for (SubtitleTrack track: mTracks) { + MediaFormat format = track.getFormat(); + String language = format.getString(MediaFormat.KEY_LANGUAGE); + boolean forced = + format.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0; + boolean autoselect = + format.getInteger(MediaFormat.KEY_IS_AUTOSELECT, 1) != 0; + boolean is_default = + format.getInteger(MediaFormat.KEY_IS_DEFAULT, 0) != 0; + + boolean languageMatches = + (locale == null || + locale.getLanguage().equals("") || + locale.getISO3Language().equals(language) || + locale.getLanguage().equals(language)); + // is_default is meaningless unless caption language is 'default' + int score = (forced ? 0 : 8) + + (((selectedLocale == null) && is_default) ? 4 : 0) + + (autoselect ? 0 : 2) + (languageMatches ? 1 : 0); + + if (selectForced && !forced) { + continue; + } + + // we treat null locale/language as matching any language + if ((selectedLocale == null && is_default) || + (languageMatches && + (autoselect || forced || selectedLocale != null))) { + if (score > bestScore) { + bestScore = score; + bestTrack = track; + } + } + } + } + return bestTrack; + } + + private boolean mTrackIsExplicit = false; + private boolean mVisibilityIsExplicit = false; + + /** @hide - should be called from anchor thread */ + public void selectDefaultTrack() { + processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK)); + } + + private void doSelectDefaultTrack() { + if (mTrackIsExplicit) { + // If track selection is explicit, but visibility + // is not, it falls back to the captioning setting + if (!mVisibilityIsExplicit) { + if (mCaptioningManager.isEnabled() || + (mSelectedTrack != null && + mSelectedTrack.getFormat().getInteger( + MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) { + show(); + } else { + hide(); + } + mVisibilityIsExplicit = false; + } + return; + } + + // We can have a default (forced) track even if captioning + // is not enabled. This is handled by getDefaultTrack(). + // Show this track unless subtitles were explicitly hidden. + SubtitleTrack track = getDefaultTrack(); + if (track != null) { + selectTrack(track); + mTrackIsExplicit = false; + if (!mVisibilityIsExplicit) { + show(); + mVisibilityIsExplicit = false; + } + } + } + + /** @hide - must be called from anchor thread */ + public void reset() { + checkAnchorLooper(); + hide(); + selectTrack(null); + mTracks.clear(); + mTrackIsExplicit = false; + mVisibilityIsExplicit = false; + mCaptioningManager.removeCaptioningChangeListener( + mCaptioningChangeListener); + } + + /** + * Adds a new, external subtitle track to the manager. + * + * @param format the format of the track that will include at least + * the MIME type {@link MediaFormat@KEY_MIME}. + * @return the created {@link SubtitleTrack} object + */ + public SubtitleTrack addTrack(MediaFormat format) { + synchronized(mRenderers) { + for (Renderer renderer: mRenderers) { + if (renderer.supports(format)) { + SubtitleTrack track = renderer.createTrack(format); + if (track != null) { + synchronized(mTracks) { + if (mTracks.size() == 0) { + mCaptioningManager.addCaptioningChangeListener( + mCaptioningChangeListener); + } + mTracks.add(track); + } + return track; + } + } + } + } + return null; + } + + /** + * Show the selected (or default) subtitle track. + * + * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper} + */ + public void show() { + processOnAnchor(mHandler.obtainMessage(WHAT_SHOW)); + } + + private void doShow() { + mShowing = true; + mVisibilityIsExplicit = true; + if (mSelectedTrack != null) { + mSelectedTrack.show(); + } + } + + /** + * Hide the selected (or default) subtitle track. + * + * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper} + */ + public void hide() { + processOnAnchor(mHandler.obtainMessage(WHAT_HIDE)); + } + + private void doHide() { + mVisibilityIsExplicit = true; + if (mSelectedTrack != null) { + mSelectedTrack.hide(); + } + mShowing = false; + } + + /** + * Interface for supporting a single or multiple subtitle types in {@link + * MediaPlayer}. + */ + public abstract static class Renderer { + /** + * Called by {@link MediaPlayer}'s {@link SubtitleController} when a new + * subtitle track is detected, to see if it should use this object to + * parse and display this subtitle track. + * + * @param format the format of the track that will include at least + * the MIME type {@link MediaFormat@KEY_MIME}. + * + * @return true if and only if the track format is supported by this + * renderer + */ + public abstract boolean supports(MediaFormat format); + + /** + * Called by {@link MediaPlayer}'s {@link SubtitleController} for each + * subtitle track that was detected and is supported by this object to + * create a {@link SubtitleTrack} object. This object will be created + * for each track that was found. If the track is selected for display, + * this object will be used to parse and display the track data. + * + * @param format the format of the track that will include at least + * the MIME type {@link MediaFormat@KEY_MIME}. + * @return a {@link SubtitleTrack} object that will be used to parse + * and render the subtitle track. + */ + public abstract SubtitleTrack createTrack(MediaFormat format); + } + + /** + * Add support for a subtitle format in {@link MediaPlayer}. + * + * @param renderer a {@link SubtitleController.Renderer} object that adds + * support for a subtitle format. + */ + public void registerRenderer(Renderer renderer) { + synchronized(mRenderers) { + // TODO how to get available renderers in the system + if (!mRenderers.contains(renderer)) { + // TODO should added renderers override existing ones (to allow replacing?) + mRenderers.add(renderer); + } + } + } + + /** + * Subtitle anchor, an object that is able to display a subtitle renderer, + * e.g. a VideoView. + */ + public interface Anchor { + /** + * Anchor should use the supplied subtitle rendering widget, or + * none if it is null. + * @hide + */ + public void setSubtitleWidget(RenderingWidget subtitleWidget); + + /** + * Anchors provide the looper on which all track visibility changes + * (track.show/hide, setSubtitleWidget) will take place. + * @hide + */ + public Looper getSubtitleLooper(); + } + + private Anchor mAnchor; + + /** + * @hide - called from anchor's looper (if any, both when unsetting and + * setting) + */ + public void setAnchor(Anchor anchor) { + if (mAnchor == anchor) { + return; + } + + if (mAnchor != null) { + checkAnchorLooper(); + mAnchor.setSubtitleWidget(null); + } + mAnchor = anchor; + mHandler = null; + if (mAnchor != null) { + mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback); + checkAnchorLooper(); + mAnchor.setSubtitleWidget(getRenderingWidget()); + } + } + + private void checkAnchorLooper() { + assert mHandler != null : "Should have a looper already"; + assert Looper.myLooper() == mHandler.getLooper() : "Must be called from the anchor's looper"; + } + + private void processOnAnchor(Message m) { + assert mHandler != null : "Should have a looper already"; + if (Looper.myLooper() == mHandler.getLooper()) { + mHandler.dispatchMessage(m); + } else { + mHandler.sendMessage(m); + } + } + + public interface Listener { + /** + * Called when a subtitle track has been selected. + * + * @param track selected subtitle track or null + * @hide + */ + public void onSubtitleTrackSelected(SubtitleTrack track); + } + + private Listener mListener; +} diff --git a/media/java/android/media/SubtitleData.java b/media/java/android/media/SubtitleData.java new file mode 100644 index 0000000000000000000000000000000000000000..f552e82ea996dad2d885c5760e50b40f9c75ee24 --- /dev/null +++ b/media/java/android/media/SubtitleData.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2011 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.media; + +import android.os.Parcel; +import android.util.Log; + +/** + * @hide + * + * Class to hold the subtitle track's data, including: + *
          + *
        • Track index
        • + *
        • Start time (in microseconds) of the data
        • + *
        • Duration (in microseconds) of the data
        • + *
        • A byte-array of the data
        • + *
        + * + *

        To receive the subtitle data, applications need to do the following: + * + *

          + *
        • Select a track of type MEDIA_TRACK_TYPE_SUBTITLE with {@link MediaPlayer.selectTrack(int)
        • + *
        • Implement the {@link MediaPlayer.OnSubtitleDataListener} interface
        • + *
        • Register the {@link MediaPlayer.OnSubtitleDataListener} callback on a MediaPlayer object
        • + *
        + * + * @see android.media.MediaPlayer + */ +public final class SubtitleData +{ + private static final String TAG = "SubtitleData"; + + private int mTrackIndex; + private long mStartTimeUs; + private long mDurationUs; + private byte[] mData; + + public SubtitleData(Parcel parcel) { + if (!parseParcel(parcel)) { + throw new IllegalArgumentException("parseParcel() fails"); + } + } + + public int getTrackIndex() { + return mTrackIndex; + } + + public long getStartTimeUs() { + return mStartTimeUs; + } + + public long getDurationUs() { + return mDurationUs; + } + + public byte[] getData() { + return mData; + } + + private boolean parseParcel(Parcel parcel) { + parcel.setDataPosition(0); + if (parcel.dataAvail() == 0) { + return false; + } + + mTrackIndex = parcel.readInt(); + mStartTimeUs = parcel.readLong(); + mDurationUs = parcel.readLong(); + mData = new byte[parcel.readInt()]; + parcel.readByteArray(mData); + + return true; + } +} diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java new file mode 100644 index 0000000000000000000000000000000000000000..06063de66a0ba0192a7963e72161487c05c788ee --- /dev/null +++ b/media/java/android/media/SubtitleTrack.java @@ -0,0 +1,705 @@ +/* + * 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.media; + +import android.graphics.Canvas; +import android.os.Handler; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.Pair; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.Vector; + +/** + * A subtitle track abstract base class that is responsible for parsing and displaying + * an instance of a particular type of subtitle. + * + * @hide + */ +public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeListener { + private static final String TAG = "SubtitleTrack"; + private long mLastUpdateTimeMs; + private long mLastTimeMs; + + private Runnable mRunnable; + + /** @hide TODO private */ + final protected LongSparseArray mRunsByEndTime = new LongSparseArray(); + /** @hide TODO private */ + final protected LongSparseArray mRunsByID = new LongSparseArray(); + + /** @hide TODO private */ + protected CueList mCues; + /** @hide TODO private */ + final protected Vector mActiveCues = new Vector(); + /** @hide */ + protected boolean mVisible; + + /** @hide */ + public boolean DEBUG = false; + + /** @hide */ + protected Handler mHandler = new Handler(); + + private MediaFormat mFormat; + + public SubtitleTrack(MediaFormat format) { + mFormat = format; + mCues = new CueList(); + clearActiveCues(); + mLastTimeMs = -1; + } + + /** @hide */ + public final MediaFormat getFormat() { + return mFormat; + } + + private long mNextScheduledTimeMs = -1; + + /** + * Called when there is input data for the subtitle track. The + * complete subtitle for a track can include multiple whole units + * (runs). Each of these units can have multiple sections. The + * contents of a run are submitted in sequential order, with eos + * indicating the last section of the run. Calls from different + * runs must not be intermixed. + * + * @param data + * @param eos true if this is the last section of the run. + * @param runID mostly-unique ID for this run of data. Subtitle cues + * with runID of 0 are discarded immediately after + * display. Cues with runID of ~0 are discarded + * only at the deletion of the track object. Cues + * with other runID-s are discarded at the end of the + * run, which defaults to the latest timestamp of + * any of its cues (with this runID). + * + * TODO use ByteBuffer + */ + public abstract void onData(String data, boolean eos, long runID); + + /** + * Called when adding the subtitle rendering widget to the view hierarchy, + * as well as when showing or hiding the subtitle track, or when the video + * surface position has changed. + * + * @return the widget that renders this subtitle track. For most renderers + * there should be a single shared instance that is used for all + * tracks supported by that renderer, as at most one subtitle track + * is visible at one time. + */ + public abstract RenderingWidget getRenderingWidget(); + + /** + * Called when the active cues have changed, and the contents of the subtitle + * view should be updated. + * + * @hide + */ + public abstract void updateView(Vector activeCues); + + /** @hide */ + protected synchronized void updateActiveCues(boolean rebuild, long timeMs) { + // out-of-order times mean seeking or new active cues being added + // (during their own timespan) + if (rebuild || mLastUpdateTimeMs > timeMs) { + clearActiveCues(); + } + + for(Iterator > it = + mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) { + Pair event = it.next(); + Cue cue = event.second; + + if (cue.mEndTimeMs == event.first) { + // remove past cues + if (DEBUG) Log.v(TAG, "Removing " + cue); + mActiveCues.remove(cue); + if (cue.mRunID == 0) { + it.remove(); + } + } else if (cue.mStartTimeMs == event.first) { + // add new cues + // TRICKY: this will happen in start order + if (DEBUG) Log.v(TAG, "Adding " + cue); + if (cue.mInnerTimesMs != null) { + cue.onTime(timeMs); + } + mActiveCues.add(cue); + } else if (cue.mInnerTimesMs != null) { + // cue is modified + cue.onTime(timeMs); + } + } + + /* complete any runs */ + while (mRunsByEndTime.size() > 0 && + mRunsByEndTime.keyAt(0) <= timeMs) { + removeRunsByEndTimeIndex(0); // removes element + } + mLastUpdateTimeMs = timeMs; + } + + private void removeRunsByEndTimeIndex(int ix) { + Run run = mRunsByEndTime.valueAt(ix); + while (run != null) { + Cue cue = run.mFirstCue; + while (cue != null) { + mCues.remove(cue); + Cue nextCue = cue.mNextInRun; + cue.mNextInRun = null; + cue = nextCue; + } + mRunsByID.remove(run.mRunID); + Run nextRun = run.mNextRunAtEndTimeMs; + run.mPrevRunAtEndTimeMs = null; + run.mNextRunAtEndTimeMs = null; + run = nextRun; + } + mRunsByEndTime.removeAt(ix); + } + + @Override + protected void finalize() throws Throwable { + /* remove all cues (untangle all cross-links) */ + int size = mRunsByEndTime.size(); + for(int ix = size - 1; ix >= 0; ix--) { + removeRunsByEndTimeIndex(ix); + } + + super.finalize(); + } + + private synchronized void takeTime(long timeMs) { + mLastTimeMs = timeMs; + } + + /** @hide */ + protected synchronized void clearActiveCues() { + if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues"); + mActiveCues.clear(); + mLastUpdateTimeMs = -1; + } + + /** @hide */ + protected void scheduleTimedEvents() { + /* get times for the next event */ + if (mTimeProvider != null) { + mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs); + if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs); + mTimeProvider.notifyAt( + mNextScheduledTimeMs >= 0 ? + (mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME, + this); + } + } + + /** + * @hide + */ + @Override + public void onTimedEvent(long timeUs) { + if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs); + synchronized (this) { + long timeMs = timeUs / 1000; + updateActiveCues(false, timeMs); + takeTime(timeMs); + } + updateView(mActiveCues); + scheduleTimedEvents(); + } + + /** + * @hide + */ + @Override + public void onSeek(long timeUs) { + if (DEBUG) Log.d(TAG, "onSeek " + timeUs); + synchronized (this) { + long timeMs = timeUs / 1000; + updateActiveCues(true, timeMs); + takeTime(timeMs); + } + updateView(mActiveCues); + scheduleTimedEvents(); + } + + /** + * @hide + */ + @Override + public void onStop() { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onStop"); + clearActiveCues(); + mLastTimeMs = -1; + } + updateView(mActiveCues); + mNextScheduledTimeMs = -1; + mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this); + } + + /** @hide */ + protected MediaTimeProvider mTimeProvider; + + /** @hide */ + public void show() { + if (mVisible) { + return; + } + + mVisible = true; + getRenderingWidget().setVisible(true); + if (mTimeProvider != null) { + mTimeProvider.scheduleUpdate(this); + } + } + + /** @hide */ + public void hide() { + if (!mVisible) { + return; + } + + if (mTimeProvider != null) { + mTimeProvider.cancelNotifications(this); + } + getRenderingWidget().setVisible(false); + mVisible = false; + } + + /** @hide */ + protected synchronized boolean addCue(Cue cue) { + mCues.add(cue); + + if (cue.mRunID != 0) { + Run run = mRunsByID.get(cue.mRunID); + if (run == null) { + run = new Run(); + mRunsByID.put(cue.mRunID, run); + run.mEndTimeMs = cue.mEndTimeMs; + } else if (run.mEndTimeMs < cue.mEndTimeMs) { + run.mEndTimeMs = cue.mEndTimeMs; + } + + // link-up cues in the same run + cue.mNextInRun = run.mFirstCue; + run.mFirstCue = cue; + } + + // if a cue is added that should be visible, need to refresh view + long nowMs = -1; + if (mTimeProvider != null) { + try { + nowMs = mTimeProvider.getCurrentTimeUs( + false /* precise */, true /* monotonic */) / 1000; + } catch (IllegalStateException e) { + // handle as it we are not playing + } + } + + if (DEBUG) Log.v(TAG, "mVisible=" + mVisible + ", " + + cue.mStartTimeMs + " <= " + nowMs + ", " + + cue.mEndTimeMs + " >= " + mLastTimeMs); + + if (mVisible && + cue.mStartTimeMs <= nowMs && + // we don't trust nowMs, so check any cue since last callback + cue.mEndTimeMs >= mLastTimeMs) { + if (mRunnable != null) { + mHandler.removeCallbacks(mRunnable); + } + final SubtitleTrack track = this; + final long thenMs = nowMs; + mRunnable = new Runnable() { + @Override + public void run() { + // even with synchronized, it is possible that we are going + // to do multiple updates as the runnable could be already + // running. + synchronized (track) { + mRunnable = null; + updateActiveCues(true, thenMs); + updateView(mActiveCues); + } + } + }; + // delay update so we don't update view on every cue. TODO why 10? + if (mHandler.postDelayed(mRunnable, 10 /* delay */)) { + if (DEBUG) Log.v(TAG, "scheduling update"); + } else { + if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update"); + } + return true; + } + + if (mVisible && + cue.mEndTimeMs >= mLastTimeMs && + (cue.mStartTimeMs < mNextScheduledTimeMs || + mNextScheduledTimeMs < 0)) { + scheduleTimedEvents(); + } + + return false; + } + + /** @hide */ + public synchronized void setTimeProvider(MediaTimeProvider timeProvider) { + if (mTimeProvider == timeProvider) { + return; + } + if (mTimeProvider != null) { + mTimeProvider.cancelNotifications(this); + } + mTimeProvider = timeProvider; + if (mTimeProvider != null) { + mTimeProvider.scheduleUpdate(this); + } + } + + + /** @hide */ + static class CueList { + private static final String TAG = "CueList"; + // simplistic, inefficient implementation + private SortedMap > mCues; + public boolean DEBUG = false; + + private boolean addEvent(Cue cue, long timeMs) { + Vector cues = mCues.get(timeMs); + if (cues == null) { + cues = new Vector(2); + mCues.put(timeMs, cues); + } else if (cues.contains(cue)) { + // do not duplicate cues + return false; + } + + cues.add(cue); + return true; + } + + private void removeEvent(Cue cue, long timeMs) { + Vector cues = mCues.get(timeMs); + if (cues != null) { + cues.remove(cue); + if (cues.size() == 0) { + mCues.remove(timeMs); + } + } + } + + public void add(Cue cue) { + // ignore non-positive-duration cues + if (cue.mStartTimeMs >= cue.mEndTimeMs) + return; + + if (!addEvent(cue, cue.mStartTimeMs)) { + return; + } + + long lastTimeMs = cue.mStartTimeMs; + if (cue.mInnerTimesMs != null) { + for (long timeMs: cue.mInnerTimesMs) { + if (timeMs > lastTimeMs && timeMs < cue.mEndTimeMs) { + addEvent(cue, timeMs); + lastTimeMs = timeMs; + } + } + } + + addEvent(cue, cue.mEndTimeMs); + } + + public void remove(Cue cue) { + removeEvent(cue, cue.mStartTimeMs); + if (cue.mInnerTimesMs != null) { + for (long timeMs: cue.mInnerTimesMs) { + removeEvent(cue, timeMs); + } + } + removeEvent(cue, cue.mEndTimeMs); + } + + public Iterable> entriesBetween( + final long lastTimeMs, final long timeMs) { + return new Iterable >() { + @Override + public Iterator > iterator() { + if (DEBUG) Log.d(TAG, "slice (" + lastTimeMs + ", " + timeMs + "]="); + try { + return new EntryIterator( + mCues.subMap(lastTimeMs + 1, timeMs + 1)); + } catch(IllegalArgumentException e) { + return new EntryIterator(null); + } + } + }; + } + + public long nextTimeAfter(long timeMs) { + SortedMap> tail = null; + try { + tail = mCues.tailMap(timeMs + 1); + if (tail != null) { + return tail.firstKey(); + } else { + return -1; + } + } catch(IllegalArgumentException e) { + return -1; + } catch(NoSuchElementException e) { + return -1; + } + } + + class EntryIterator implements Iterator > { + @Override + public boolean hasNext() { + return !mDone; + } + + @Override + public Pair next() { + if (mDone) { + throw new NoSuchElementException(""); + } + mLastEntry = new Pair( + mCurrentTimeMs, mListIterator.next()); + mLastListIterator = mListIterator; + if (!mListIterator.hasNext()) { + nextKey(); + } + return mLastEntry; + } + + @Override + public void remove() { + // only allow removing end tags + if (mLastListIterator == null || + mLastEntry.second.mEndTimeMs != mLastEntry.first) { + throw new IllegalStateException(""); + } + + // remove end-cue + mLastListIterator.remove(); + mLastListIterator = null; + if (mCues.get(mLastEntry.first).size() == 0) { + mCues.remove(mLastEntry.first); + } + + // remove rest of the cues + Cue cue = mLastEntry.second; + removeEvent(cue, cue.mStartTimeMs); + if (cue.mInnerTimesMs != null) { + for (long timeMs: cue.mInnerTimesMs) { + removeEvent(cue, timeMs); + } + } + } + + public EntryIterator(SortedMap > cues) { + if (DEBUG) Log.v(TAG, cues + ""); + mRemainingCues = cues; + mLastListIterator = null; + nextKey(); + } + + private void nextKey() { + do { + try { + if (mRemainingCues == null) { + throw new NoSuchElementException(""); + } + mCurrentTimeMs = mRemainingCues.firstKey(); + mListIterator = + mRemainingCues.get(mCurrentTimeMs).iterator(); + try { + mRemainingCues = + mRemainingCues.tailMap(mCurrentTimeMs + 1); + } catch (IllegalArgumentException e) { + mRemainingCues = null; + } + mDone = false; + } catch (NoSuchElementException e) { + mDone = true; + mRemainingCues = null; + mListIterator = null; + return; + } + } while (!mListIterator.hasNext()); + } + + private long mCurrentTimeMs; + private Iterator mListIterator; + private boolean mDone; + private SortedMap > mRemainingCues; + private Iterator mLastListIterator; + private Pair mLastEntry; + } + + CueList() { + mCues = new TreeMap>(); + } + } + + /** @hide */ + public static class Cue { + public long mStartTimeMs; + public long mEndTimeMs; + public long[] mInnerTimesMs; + public long mRunID; + + /** @hide */ + public Cue mNextInRun; + + public void onTime(long timeMs) { } + } + + /** @hide update mRunsByEndTime (with default end time) */ + protected void finishedRun(long runID) { + if (runID != 0 && runID != ~0) { + Run run = mRunsByID.get(runID); + if (run != null) { + run.storeByEndTimeMs(mRunsByEndTime); + } + } + } + + /** @hide update mRunsByEndTime with given end time */ + public void setRunDiscardTimeMs(long runID, long timeMs) { + if (runID != 0 && runID != ~0) { + Run run = mRunsByID.get(runID); + if (run != null) { + run.mEndTimeMs = timeMs; + run.storeByEndTimeMs(mRunsByEndTime); + } + } + } + + /** @hide */ + private static class Run { + public Cue mFirstCue; + public Run mNextRunAtEndTimeMs; + public Run mPrevRunAtEndTimeMs; + public long mEndTimeMs = -1; + public long mRunID = 0; + private long mStoredEndTimeMs = -1; + + public void storeByEndTimeMs(LongSparseArray runsByEndTime) { + // remove old value if any + int ix = runsByEndTime.indexOfKey(mStoredEndTimeMs); + if (ix >= 0) { + if (mPrevRunAtEndTimeMs == null) { + assert(this == runsByEndTime.valueAt(ix)); + if (mNextRunAtEndTimeMs == null) { + runsByEndTime.removeAt(ix); + } else { + runsByEndTime.setValueAt(ix, mNextRunAtEndTimeMs); + } + } + removeAtEndTimeMs(); + } + + // add new value + if (mEndTimeMs >= 0) { + mPrevRunAtEndTimeMs = null; + mNextRunAtEndTimeMs = runsByEndTime.get(mEndTimeMs); + if (mNextRunAtEndTimeMs != null) { + mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = this; + } + runsByEndTime.put(mEndTimeMs, this); + mStoredEndTimeMs = mEndTimeMs; + } + } + + public void removeAtEndTimeMs() { + Run prev = mPrevRunAtEndTimeMs; + + if (mPrevRunAtEndTimeMs != null) { + mPrevRunAtEndTimeMs.mNextRunAtEndTimeMs = mNextRunAtEndTimeMs; + mPrevRunAtEndTimeMs = null; + } + if (mNextRunAtEndTimeMs != null) { + mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = prev; + mNextRunAtEndTimeMs = null; + } + } + } + + /** + * Interface for rendering subtitles onto a Canvas. + */ + public interface RenderingWidget { + /** + * Sets the widget's callback, which is used to send updates when the + * rendered data has changed. + * + * @param callback update callback + */ + public void setOnChangedListener(OnChangedListener callback); + + /** + * Sets the widget's size. + * + * @param width width in pixels + * @param height height in pixels + */ + public void setSize(int width, int height); + + /** + * Sets whether the widget should draw subtitles. + * + * @param visible true if subtitles should be drawn, false otherwise + */ + public void setVisible(boolean visible); + + /** + * Renders subtitles onto a {@link Canvas}. + * + * @param c canvas on which to render subtitles + */ + public void draw(Canvas c); + + /** + * Called when the widget is attached to a window. + */ + public void onAttachedToWindow(); + + /** + * Called when the widget is detached from a window. + */ + public void onDetachedFromWindow(); + + /** + * Callback used to send updates about changes to rendering data. + */ + public interface OnChangedListener { + /** + * Called when the rendering data has changed. + * + * @param renderingWidget the widget whose data has changed + */ + public void onChanged(RenderingWidget renderingWidget); + } + } +} diff --git a/media/java/android/media/VolumeController.java b/media/java/android/media/VolumeController.java new file mode 100644 index 0000000000000000000000000000000000000000..2d12bf25f4d1081583a5b6304bb70c51b5022e9b --- /dev/null +++ b/media/java/android/media/VolumeController.java @@ -0,0 +1,29 @@ +/* + * 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.media; + +/** + * @hide + */ +public interface VolumeController { + + public void postHasNewRemotePlaybackInfo(); + + public void postRemoteVolumeChanged(int streamType, int flags); + + public void postRemoteSliderVisibility(boolean visible); +} diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..4dec0819d8dd10aa3bb01c85d20a71e30eb3d345 --- /dev/null +++ b/media/java/android/media/WebVttRenderer.java @@ -0,0 +1,1842 @@ +/* + * 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.media; + +import android.content.Context; +import android.text.Layout.Alignment; +import android.text.SpannableStringBuilder; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.CaptioningManager; +import android.view.accessibility.CaptioningManager.CaptionStyle; +import android.view.accessibility.CaptioningManager.CaptioningChangeListener; +import android.widget.LinearLayout; + +import com.android.internal.widget.SubtitleView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +/** @hide */ +public class WebVttRenderer extends SubtitleController.Renderer { + private final Context mContext; + + private WebVttRenderingWidget mRenderingWidget; + + public WebVttRenderer(Context context) { + mContext = context; + } + + @Override + public boolean supports(MediaFormat format) { + if (format.containsKey(MediaFormat.KEY_MIME)) { + return format.getString(MediaFormat.KEY_MIME).equals("text/vtt"); + } + return false; + } + + @Override + public SubtitleTrack createTrack(MediaFormat format) { + if (mRenderingWidget == null) { + mRenderingWidget = new WebVttRenderingWidget(mContext); + } + + return new WebVttTrack(mRenderingWidget, format); + } +} + +/** @hide */ +class TextTrackCueSpan { + long mTimestampMs; + boolean mEnabled; + String mText; + TextTrackCueSpan(String text, long timestamp) { + mTimestampMs = timestamp; + mText = text; + // spans with timestamp will be enabled by Cue.onTime + mEnabled = (mTimestampMs < 0); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TextTrackCueSpan)) { + return false; + } + TextTrackCueSpan span = (TextTrackCueSpan) o; + return mTimestampMs == span.mTimestampMs && + mText.equals(span.mText); + } +} + +/** + * @hide + * + * Extract all text without style, but with timestamp spans. + */ +class UnstyledTextExtractor implements Tokenizer.OnTokenListener { + StringBuilder mLine = new StringBuilder(); + Vector mLines = new Vector(); + Vector mCurrentLine = new Vector(); + long mLastTimestamp; + + UnstyledTextExtractor() { + init(); + } + + private void init() { + mLine.delete(0, mLine.length()); + mLines.clear(); + mCurrentLine.clear(); + mLastTimestamp = -1; + } + + @Override + public void onData(String s) { + mLine.append(s); + } + + @Override + public void onStart(String tag, String[] classes, String annotation) { } + + @Override + public void onEnd(String tag) { } + + @Override + public void onTimeStamp(long timestampMs) { + // finish any prior span + if (mLine.length() > 0 && timestampMs != mLastTimestamp) { + mCurrentLine.add( + new TextTrackCueSpan(mLine.toString(), mLastTimestamp)); + mLine.delete(0, mLine.length()); + } + mLastTimestamp = timestampMs; + } + + @Override + public void onLineEnd() { + // finish any pending span + if (mLine.length() > 0) { + mCurrentLine.add( + new TextTrackCueSpan(mLine.toString(), mLastTimestamp)); + mLine.delete(0, mLine.length()); + } + + TextTrackCueSpan[] spans = new TextTrackCueSpan[mCurrentLine.size()]; + mCurrentLine.toArray(spans); + mCurrentLine.clear(); + mLines.add(spans); + } + + public TextTrackCueSpan[][] getText() { + // for politeness, finish last cue-line if it ends abruptly + if (mLine.length() > 0 || mCurrentLine.size() > 0) { + onLineEnd(); + } + TextTrackCueSpan[][] lines = new TextTrackCueSpan[mLines.size()][]; + mLines.toArray(lines); + init(); + return lines; + } +} + +/** + * @hide + * + * Tokenizer tokenizes the WebVTT Cue Text into tags and data + */ +class Tokenizer { + private static final String TAG = "Tokenizer"; + private TokenizerPhase mPhase; + private TokenizerPhase mDataTokenizer; + private TokenizerPhase mTagTokenizer; + + private OnTokenListener mListener; + private String mLine; + private int mHandledLen; + + interface TokenizerPhase { + TokenizerPhase start(); + void tokenize(); + } + + class DataTokenizer implements TokenizerPhase { + // includes both WebVTT data && escape state + private StringBuilder mData; + + public TokenizerPhase start() { + mData = new StringBuilder(); + return this; + } + + private boolean replaceEscape(String escape, String replacement, int pos) { + if (mLine.startsWith(escape, pos)) { + mData.append(mLine.substring(mHandledLen, pos)); + mData.append(replacement); + mHandledLen = pos + escape.length(); + pos = mHandledLen - 1; + return true; + } + return false; + } + + @Override + public void tokenize() { + int end = mLine.length(); + for (int pos = mHandledLen; pos < mLine.length(); pos++) { + if (mLine.charAt(pos) == '&') { + if (replaceEscape("&", "&", pos) || + replaceEscape("<", "<", pos) || + replaceEscape(">", ">", pos) || + replaceEscape("‎", "\u200e", pos) || + replaceEscape("‏", "\u200f", pos) || + replaceEscape(" ", "\u00a0", pos)) { + continue; + } + } else if (mLine.charAt(pos) == '<') { + end = pos; + mPhase = mTagTokenizer.start(); + break; + } + } + mData.append(mLine.substring(mHandledLen, end)); + // yield mData + mListener.onData(mData.toString()); + mData.delete(0, mData.length()); + mHandledLen = end; + } + } + + class TagTokenizer implements TokenizerPhase { + private boolean mAtAnnotation; + private String mName, mAnnotation; + + public TokenizerPhase start() { + mName = mAnnotation = ""; + mAtAnnotation = false; + return this; + } + + @Override + public void tokenize() { + if (!mAtAnnotation) + mHandledLen++; + if (mHandledLen < mLine.length()) { + String[] parts; + /** + * Collect annotations and end-tags to closing >. Collect tag + * name to closing bracket or next white-space. + */ + if (mAtAnnotation || mLine.charAt(mHandledLen) == '/') { + parts = mLine.substring(mHandledLen).split(">"); + } else { + parts = mLine.substring(mHandledLen).split("[\t\f >]"); + } + String part = mLine.substring( + mHandledLen, mHandledLen + parts[0].length()); + mHandledLen += parts[0].length(); + + if (mAtAnnotation) { + mAnnotation += " " + part; + } else { + mName = part; + } + } + + mAtAnnotation = true; + + if (mHandledLen < mLine.length() && mLine.charAt(mHandledLen) == '>') { + yield_tag(); + mPhase = mDataTokenizer.start(); + mHandledLen++; + } + } + + private void yield_tag() { + if (mName.startsWith("/")) { + mListener.onEnd(mName.substring(1)); + } else if (mName.length() > 0 && Character.isDigit(mName.charAt(0))) { + // timestamp + try { + long timestampMs = WebVttParser.parseTimestampMs(mName); + mListener.onTimeStamp(timestampMs); + } catch (NumberFormatException e) { + Log.d(TAG, "invalid timestamp tag: <" + mName + ">"); + } + } else { + mAnnotation = mAnnotation.replaceAll("\\s+", " "); + if (mAnnotation.startsWith(" ")) { + mAnnotation = mAnnotation.substring(1); + } + if (mAnnotation.endsWith(" ")) { + mAnnotation = mAnnotation.substring(0, mAnnotation.length() - 1); + } + + String[] classes = null; + int dotAt = mName.indexOf('.'); + if (dotAt >= 0) { + classes = mName.substring(dotAt + 1).split("\\."); + mName = mName.substring(0, dotAt); + } + mListener.onStart(mName, classes, mAnnotation); + } + } + } + + Tokenizer(OnTokenListener listener) { + mDataTokenizer = new DataTokenizer(); + mTagTokenizer = new TagTokenizer(); + reset(); + mListener = listener; + } + + void reset() { + mPhase = mDataTokenizer.start(); + } + + void tokenize(String s) { + mHandledLen = 0; + mLine = s; + while (mHandledLen < mLine.length()) { + mPhase.tokenize(); + } + /* we are finished with a line unless we are in the middle of a tag */ + if (!(mPhase instanceof TagTokenizer)) { + // yield END-OF-LINE + mListener.onLineEnd(); + } + } + + interface OnTokenListener { + void onData(String s); + void onStart(String tag, String[] classes, String annotation); + void onEnd(String tag); + void onTimeStamp(long timestampMs); + void onLineEnd(); + } +} + +/** @hide */ +class TextTrackRegion { + final static int SCROLL_VALUE_NONE = 300; + final static int SCROLL_VALUE_SCROLL_UP = 301; + + String mId; + float mWidth; + int mLines; + float mAnchorPointX, mAnchorPointY; + float mViewportAnchorPointX, mViewportAnchorPointY; + int mScrollValue; + + TextTrackRegion() { + mId = ""; + mWidth = 100; + mLines = 3; + mAnchorPointX = mViewportAnchorPointX = 0.f; + mAnchorPointY = mViewportAnchorPointY = 100.f; + mScrollValue = SCROLL_VALUE_NONE; + } + + public String toString() { + StringBuilder res = new StringBuilder(" {id:\"").append(mId) + .append("\", width:").append(mWidth) + .append(", lines:").append(mLines) + .append(", anchorPoint:(").append(mAnchorPointX) + .append(", ").append(mAnchorPointY) + .append("), viewportAnchorPoints:").append(mViewportAnchorPointX) + .append(", ").append(mViewportAnchorPointY) + .append("), scrollValue:") + .append(mScrollValue == SCROLL_VALUE_NONE ? "none" : + mScrollValue == SCROLL_VALUE_SCROLL_UP ? "scroll_up" : + "INVALID") + .append("}"); + return res.toString(); + } +} + +/** @hide */ +class TextTrackCue extends SubtitleTrack.Cue { + final static int WRITING_DIRECTION_HORIZONTAL = 100; + final static int WRITING_DIRECTION_VERTICAL_RL = 101; + final static int WRITING_DIRECTION_VERTICAL_LR = 102; + + final static int ALIGNMENT_MIDDLE = 200; + final static int ALIGNMENT_START = 201; + final static int ALIGNMENT_END = 202; + final static int ALIGNMENT_LEFT = 203; + final static int ALIGNMENT_RIGHT = 204; + private static final String TAG = "TTCue"; + + String mId; + boolean mPauseOnExit; + int mWritingDirection; + String mRegionId; + boolean mSnapToLines; + Integer mLinePosition; // null means AUTO + boolean mAutoLinePosition; + int mTextPosition; + int mSize; + int mAlignment; + // Vector mText; + String[] mStrings; + TextTrackCueSpan[][] mLines; + TextTrackRegion mRegion; + + TextTrackCue() { + mId = ""; + mPauseOnExit = false; + mWritingDirection = WRITING_DIRECTION_HORIZONTAL; + mRegionId = ""; + mSnapToLines = true; + mLinePosition = null /* AUTO */; + mTextPosition = 50; + mSize = 100; + mAlignment = ALIGNMENT_MIDDLE; + mLines = null; + mRegion = null; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TextTrackCue)) { + return false; + } + if (this == o) { + return true; + } + + try { + TextTrackCue cue = (TextTrackCue) o; + boolean res = mId.equals(cue.mId) && + mPauseOnExit == cue.mPauseOnExit && + mWritingDirection == cue.mWritingDirection && + mRegionId.equals(cue.mRegionId) && + mSnapToLines == cue.mSnapToLines && + mAutoLinePosition == cue.mAutoLinePosition && + (mAutoLinePosition || mLinePosition == cue.mLinePosition) && + mTextPosition == cue.mTextPosition && + mSize == cue.mSize && + mAlignment == cue.mAlignment && + mLines.length == cue.mLines.length; + if (res == true) { + for (int line = 0; line < mLines.length; line++) { + if (!Arrays.equals(mLines[line], cue.mLines[line])) { + return false; + } + } + } + return res; + } catch(IncompatibleClassChangeError e) { + return false; + } + } + + public StringBuilder appendStringsToBuilder(StringBuilder builder) { + if (mStrings == null) { + builder.append("null"); + } else { + builder.append("["); + boolean first = true; + for (String s: mStrings) { + if (!first) { + builder.append(", "); + } + if (s == null) { + builder.append("null"); + } else { + builder.append("\""); + builder.append(s); + builder.append("\""); + } + first = false; + } + builder.append("]"); + } + return builder; + } + + public StringBuilder appendLinesToBuilder(StringBuilder builder) { + if (mLines == null) { + builder.append("null"); + } else { + builder.append("["); + boolean first = true; + for (TextTrackCueSpan[] spans: mLines) { + if (!first) { + builder.append(", "); + } + if (spans == null) { + builder.append("null"); + } else { + builder.append("\""); + boolean innerFirst = true; + long lastTimestamp = -1; + for (TextTrackCueSpan span: spans) { + if (!innerFirst) { + builder.append(" "); + } + if (span.mTimestampMs != lastTimestamp) { + builder.append("<") + .append(WebVttParser.timeToString( + span.mTimestampMs)) + .append(">"); + lastTimestamp = span.mTimestampMs; + } + builder.append(span.mText); + innerFirst = false; + } + builder.append("\""); + } + first = false; + } + builder.append("]"); + } + return builder; + } + + public String toString() { + StringBuilder res = new StringBuilder(); + + res.append(WebVttParser.timeToString(mStartTimeMs)) + .append(" --> ").append(WebVttParser.timeToString(mEndTimeMs)) + .append(" {id:\"").append(mId) + .append("\", pauseOnExit:").append(mPauseOnExit) + .append(", direction:") + .append(mWritingDirection == WRITING_DIRECTION_HORIZONTAL ? "horizontal" : + mWritingDirection == WRITING_DIRECTION_VERTICAL_LR ? "vertical_lr" : + mWritingDirection == WRITING_DIRECTION_VERTICAL_RL ? "vertical_rl" : + "INVALID") + .append(", regionId:\"").append(mRegionId) + .append("\", snapToLines:").append(mSnapToLines) + .append(", linePosition:").append(mAutoLinePosition ? "auto" : + mLinePosition) + .append(", textPosition:").append(mTextPosition) + .append(", size:").append(mSize) + .append(", alignment:") + .append(mAlignment == ALIGNMENT_END ? "end" : + mAlignment == ALIGNMENT_LEFT ? "left" : + mAlignment == ALIGNMENT_MIDDLE ? "middle" : + mAlignment == ALIGNMENT_RIGHT ? "right" : + mAlignment == ALIGNMENT_START ? "start" : "INVALID") + .append(", text:"); + appendStringsToBuilder(res).append("}"); + return res.toString(); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public void onTime(long timeMs) { + for (TextTrackCueSpan[] line: mLines) { + for (TextTrackCueSpan span: line) { + span.mEnabled = timeMs >= span.mTimestampMs; + } + } + } +} + +/** @hide */ +class WebVttParser { + private static final String TAG = "WebVttParser"; + private Phase mPhase; + private TextTrackCue mCue; + private Vector mCueTexts; + private WebVttCueListener mListener; + private String mBuffer; + + WebVttParser(WebVttCueListener listener) { + mPhase = mParseStart; + mBuffer = ""; /* mBuffer contains up to 1 incomplete line */ + mListener = listener; + mCueTexts = new Vector(); + } + + /* parsePercentageString */ + public static float parseFloatPercentage(String s) + throws NumberFormatException { + if (!s.endsWith("%")) { + throw new NumberFormatException("does not end in %"); + } + s = s.substring(0, s.length() - 1); + // parseFloat allows an exponent or a sign + if (s.matches(".*[^0-9.].*")) { + throw new NumberFormatException("contains an invalid character"); + } + + try { + float value = Float.parseFloat(s); + if (value < 0.0f || value > 100.0f) { + throw new NumberFormatException("is out of range"); + } + return value; + } catch (NumberFormatException e) { + throw new NumberFormatException("is not a number"); + } + } + + public static int parseIntPercentage(String s) throws NumberFormatException { + if (!s.endsWith("%")) { + throw new NumberFormatException("does not end in %"); + } + s = s.substring(0, s.length() - 1); + // parseInt allows "-0" that returns 0, so check for non-digits + if (s.matches(".*[^0-9].*")) { + throw new NumberFormatException("contains an invalid character"); + } + + try { + int value = Integer.parseInt(s); + if (value < 0 || value > 100) { + throw new NumberFormatException("is out of range"); + } + return value; + } catch (NumberFormatException e) { + throw new NumberFormatException("is not a number"); + } + } + + public static long parseTimestampMs(String s) throws NumberFormatException { + if (!s.matches("(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}")) { + throw new NumberFormatException("has invalid format"); + } + + String[] parts = s.split("\\.", 2); + long value = 0; + for (String group: parts[0].split(":")) { + value = value * 60 + Long.parseLong(group); + } + return value * 1000 + Long.parseLong(parts[1]); + } + + public static String timeToString(long timeMs) { + return String.format("%d:%02d:%02d.%03d", + timeMs / 3600000, (timeMs / 60000) % 60, + (timeMs / 1000) % 60, timeMs % 1000); + } + + public void parse(String s) { + boolean trailingCR = false; + mBuffer = (mBuffer + s.replace("\0", "\ufffd")).replace("\r\n", "\n"); + + /* keep trailing '\r' in case matching '\n' arrives in next packet */ + if (mBuffer.endsWith("\r")) { + trailingCR = true; + mBuffer = mBuffer.substring(0, mBuffer.length() - 1); + } + + String[] lines = mBuffer.split("[\r\n]"); + for (int i = 0; i < lines.length - 1; i++) { + mPhase.parse(lines[i]); + } + + mBuffer = lines[lines.length - 1]; + if (trailingCR) + mBuffer += "\r"; + } + + public void eos() { + if (mBuffer.endsWith("\r")) { + mBuffer = mBuffer.substring(0, mBuffer.length() - 1); + } + + mPhase.parse(mBuffer); + mBuffer = ""; + + yieldCue(); + mPhase = mParseStart; + } + + public void yieldCue() { + if (mCue != null && mCueTexts.size() > 0) { + mCue.mStrings = new String[mCueTexts.size()]; + mCueTexts.toArray(mCue.mStrings); + mCueTexts.clear(); + mListener.onCueParsed(mCue); + } + mCue = null; + } + + interface Phase { + void parse(String line); + } + + final private Phase mSkipRest = new Phase() { + @Override + public void parse(String line) { } + }; + + final private Phase mParseStart = new Phase() { // 5-9 + @Override + public void parse(String line) { + if (line.startsWith("\ufeff")) { + line = line.substring(1); + } + if (!line.equals("WEBVTT") && + !line.startsWith("WEBVTT ") && + !line.startsWith("WEBVTT\t")) { + log_warning("Not a WEBVTT header", line); + mPhase = mSkipRest; + } else { + mPhase = mParseHeader; + } + } + }; + + final private Phase mParseHeader = new Phase() { // 10-13 + TextTrackRegion parseRegion(String s) { + TextTrackRegion region = new TextTrackRegion(); + for (String setting: s.split(" +")) { + int equalAt = setting.indexOf('='); + if (equalAt <= 0 || equalAt == setting.length() - 1) { + continue; + } + + String name = setting.substring(0, equalAt); + String value = setting.substring(equalAt + 1); + if (name.equals("id")) { + region.mId = value; + } else if (name.equals("width")) { + try { + region.mWidth = parseFloatPercentage(value); + } catch (NumberFormatException e) { + log_warning("region setting", name, + "has invalid value", e.getMessage(), value); + } + } else if (name.equals("lines")) { + try { + int lines = Integer.parseInt(value); + if (lines >= 0) { + region.mLines = lines; + } else { + log_warning("region setting", name, "is negative", value); + } + } catch (NumberFormatException e) { + log_warning("region setting", name, "is not numeric", value); + } + } else if (name.equals("regionanchor") || + name.equals("viewportanchor")) { + int commaAt = value.indexOf(","); + if (commaAt < 0) { + log_warning("region setting", name, "contains no comma", value); + continue; + } + + String anchorX = value.substring(0, commaAt); + String anchorY = value.substring(commaAt + 1); + float x, y; + + try { + x = parseFloatPercentage(anchorX); + } catch (NumberFormatException e) { + log_warning("region setting", name, + "has invalid x component", e.getMessage(), anchorX); + continue; + } + try { + y = parseFloatPercentage(anchorY); + } catch (NumberFormatException e) { + log_warning("region setting", name, + "has invalid y component", e.getMessage(), anchorY); + continue; + } + + if (name.charAt(0) == 'r') { + region.mAnchorPointX = x; + region.mAnchorPointY = y; + } else { + region.mViewportAnchorPointX = x; + region.mViewportAnchorPointY = y; + } + } else if (name.equals("scroll")) { + if (value.equals("up")) { + region.mScrollValue = + TextTrackRegion.SCROLL_VALUE_SCROLL_UP; + } else { + log_warning("region setting", name, "has invalid value", value); + } + } + } + return region; + } + + @Override + public void parse(String line) { + if (line.length() == 0) { + mPhase = mParseCueId; + } else if (line.contains("-->")) { + mPhase = mParseCueTime; + mPhase.parse(line); + } else { + int colonAt = line.indexOf(':'); + if (colonAt <= 0 || colonAt >= line.length() - 1) { + log_warning("meta data header has invalid format", line); + } + String name = line.substring(0, colonAt); + String value = line.substring(colonAt + 1); + + if (name.equals("Region")) { + TextTrackRegion region = parseRegion(value); + mListener.onRegionParsed(region); + } + } + } + }; + + final private Phase mParseCueId = new Phase() { + @Override + public void parse(String line) { + if (line.length() == 0) { + return; + } + + assert(mCue == null); + + if (line.equals("NOTE") || line.startsWith("NOTE ")) { + mPhase = mParseCueText; + } + + mCue = new TextTrackCue(); + mCueTexts.clear(); + + mPhase = mParseCueTime; + if (line.contains("-->")) { + mPhase.parse(line); + } else { + mCue.mId = line; + } + } + }; + + final private Phase mParseCueTime = new Phase() { + @Override + public void parse(String line) { + int arrowAt = line.indexOf("-->"); + if (arrowAt < 0) { + mCue = null; + mPhase = mParseCueId; + return; + } + + String start = line.substring(0, arrowAt).trim(); + // convert only initial and first other white-space to space + String rest = line.substring(arrowAt + 3) + .replaceFirst("^\\s+", "").replaceFirst("\\s+", " "); + int spaceAt = rest.indexOf(' '); + String end = spaceAt > 0 ? rest.substring(0, spaceAt) : rest; + rest = spaceAt > 0 ? rest.substring(spaceAt + 1) : ""; + + mCue.mStartTimeMs = parseTimestampMs(start); + mCue.mEndTimeMs = parseTimestampMs(end); + for (String setting: rest.split(" +")) { + int colonAt = setting.indexOf(':'); + if (colonAt <= 0 || colonAt == setting.length() - 1) { + continue; + } + String name = setting.substring(0, colonAt); + String value = setting.substring(colonAt + 1); + + if (name.equals("region")) { + mCue.mRegionId = value; + } else if (name.equals("vertical")) { + if (value.equals("rl")) { + mCue.mWritingDirection = + TextTrackCue.WRITING_DIRECTION_VERTICAL_RL; + } else if (value.equals("lr")) { + mCue.mWritingDirection = + TextTrackCue.WRITING_DIRECTION_VERTICAL_LR; + } else { + log_warning("cue setting", name, "has invalid value", value); + } + } else if (name.equals("line")) { + try { + int linePosition; + /* TRICKY: we know that there are no spaces in value */ + assert(value.indexOf(' ') < 0); + if (value.endsWith("%")) { + linePosition = Integer.parseInt( + value.substring(0, value.length() - 1)); + if (linePosition < 0 || linePosition > 100) { + log_warning("cue setting", name, "is out of range", value); + continue; + } + mCue.mSnapToLines = false; + mCue.mLinePosition = linePosition; + } else { + mCue.mSnapToLines = true; + mCue.mLinePosition = Integer.parseInt(value); + } + } catch (NumberFormatException e) { + log_warning("cue setting", name, + "is not numeric or percentage", value); + } + } else if (name.equals("position")) { + try { + mCue.mTextPosition = parseIntPercentage(value); + } catch (NumberFormatException e) { + log_warning("cue setting", name, + "is not numeric or percentage", value); + } + } else if (name.equals("size")) { + try { + mCue.mSize = parseIntPercentage(value); + } catch (NumberFormatException e) { + log_warning("cue setting", name, + "is not numeric or percentage", value); + } + } else if (name.equals("align")) { + if (value.equals("start")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_START; + } else if (value.equals("middle")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_MIDDLE; + } else if (value.equals("end")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_END; + } else if (value.equals("left")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_LEFT; + } else if (value.equals("right")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_RIGHT; + } else { + log_warning("cue setting", name, "has invalid value", value); + continue; + } + } + } + + if (mCue.mLinePosition != null || + mCue.mSize != 100 || + (mCue.mWritingDirection != + TextTrackCue.WRITING_DIRECTION_HORIZONTAL)) { + mCue.mRegionId = ""; + } + + mPhase = mParseCueText; + } + }; + + /* also used for notes */ + final private Phase mParseCueText = new Phase() { + @Override + public void parse(String line) { + if (line.length() == 0) { + yieldCue(); + mPhase = mParseCueId; + return; + } else if (mCue != null) { + mCueTexts.add(line); + } + } + }; + + private void log_warning( + String nameType, String name, String message, + String subMessage, String value) { + Log.w(this.getClass().getName(), nameType + " '" + name + "' " + + message + " ('" + value + "' " + subMessage + ")"); + } + + private void log_warning( + String nameType, String name, String message, String value) { + Log.w(this.getClass().getName(), nameType + " '" + name + "' " + + message + " ('" + value + "')"); + } + + private void log_warning(String message, String value) { + Log.w(this.getClass().getName(), message + " ('" + value + "')"); + } +} + +/** @hide */ +interface WebVttCueListener { + void onCueParsed(TextTrackCue cue); + void onRegionParsed(TextTrackRegion region); +} + +/** @hide */ +class WebVttTrack extends SubtitleTrack implements WebVttCueListener { + private static final String TAG = "WebVttTrack"; + + private final WebVttParser mParser = new WebVttParser(this); + private final UnstyledTextExtractor mExtractor = + new UnstyledTextExtractor(); + private final Tokenizer mTokenizer = new Tokenizer(mExtractor); + private final Vector mTimestamps = new Vector(); + private final WebVttRenderingWidget mRenderingWidget; + + private final Map mRegions = + new HashMap(); + private Long mCurrentRunID; + + WebVttTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) { + super(format); + + mRenderingWidget = renderingWidget; + } + + @Override + public WebVttRenderingWidget getRenderingWidget() { + return mRenderingWidget; + } + + @Override + public void onData(String data, boolean eos, long runID) { + // implement intermixing restriction for WebVTT only for now + synchronized(mParser) { + if (mCurrentRunID != null && runID != mCurrentRunID) { + throw new IllegalStateException( + "Run #" + mCurrentRunID + + " in progress. Cannot process run #" + runID); + } + mCurrentRunID = runID; + mParser.parse(data); + if (eos) { + finishedRun(runID); + mParser.eos(); + mRegions.clear(); + mCurrentRunID = null; + } + } + } + + @Override + public void onCueParsed(TextTrackCue cue) { + synchronized (mParser) { + // resolve region + if (cue.mRegionId.length() != 0) { + cue.mRegion = mRegions.get(cue.mRegionId); + } + + if (DEBUG) Log.v(TAG, "adding cue " + cue); + + // tokenize text track string-lines into lines of spans + mTokenizer.reset(); + for (String s: cue.mStrings) { + mTokenizer.tokenize(s); + } + cue.mLines = mExtractor.getText(); + if (DEBUG) Log.v(TAG, cue.appendLinesToBuilder( + cue.appendStringsToBuilder( + new StringBuilder()).append(" simplified to: ")) + .toString()); + + // extract inner timestamps + for (TextTrackCueSpan[] line: cue.mLines) { + for (TextTrackCueSpan span: line) { + if (span.mTimestampMs > cue.mStartTimeMs && + span.mTimestampMs < cue.mEndTimeMs && + !mTimestamps.contains(span.mTimestampMs)) { + mTimestamps.add(span.mTimestampMs); + } + } + } + + if (mTimestamps.size() > 0) { + cue.mInnerTimesMs = new long[mTimestamps.size()]; + for (int ix=0; ix < mTimestamps.size(); ++ix) { + cue.mInnerTimesMs[ix] = mTimestamps.get(ix); + } + mTimestamps.clear(); + } else { + cue.mInnerTimesMs = null; + } + + cue.mRunID = mCurrentRunID; + } + + addCue(cue); + } + + @Override + public void onRegionParsed(TextTrackRegion region) { + synchronized(mParser) { + mRegions.put(region.mId, region); + } + } + + @Override + public void updateView(Vector activeCues) { + if (!mVisible) { + // don't keep the state if we are not visible + return; + } + + if (DEBUG && mTimeProvider != null) { + try { + Log.d(TAG, "at " + + (mTimeProvider.getCurrentTimeUs(false, true) / 1000) + + " ms the active cues are:"); + } catch (IllegalStateException e) { + Log.d(TAG, "at (illegal state) the active cues are:"); + } + } + + mRenderingWidget.setActiveCues(activeCues); + } +} + +/** + * Widget capable of rendering WebVTT captions. + * + * @hide + */ +class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.RenderingWidget { + private static final boolean DEBUG = false; + private static final int DEBUG_REGION_BACKGROUND = 0x800000FF; + private static final int DEBUG_CUE_BACKGROUND = 0x80FF0000; + + /** WebVtt specifies line height as 5.3% of the viewport height. */ + private static final float LINE_HEIGHT_RATIO = 0.0533f; + + /** Map of active regions, used to determine enter/exit. */ + private final ArrayMap mRegionBoxes = + new ArrayMap(); + + /** Map of active cues, used to determine enter/exit. */ + private final ArrayMap mCueBoxes = + new ArrayMap(); + + /** Captioning manager, used to obtain and track caption properties. */ + private final CaptioningManager mManager; + + /** Callback for rendering changes. */ + private OnChangedListener mListener; + + /** Current caption style. */ + private CaptionStyle mCaptionStyle; + + /** Current font size, computed from font scaling factor and height. */ + private float mFontSize; + + /** Whether a caption style change listener is registered. */ + private boolean mHasChangeListener; + + public WebVttRenderingWidget(Context context) { + this(context, null); + } + + public WebVttRenderingWidget(Context context, AttributeSet attrs) { + this(context, null, 0); + } + + public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // Cannot render text over video when layer type is hardware. + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + + mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); + mCaptionStyle = mManager.getUserStyle(); + mFontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO; + } + + @Override + public void setSize(int width, int height) { + final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); + final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + + measure(widthSpec, heightSpec); + layout(0, 0, width, height); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + manageChangeListener(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + manageChangeListener(); + } + + @Override + public void setOnChangedListener(OnChangedListener listener) { + mListener = listener; + } + + @Override + public void setVisible(boolean visible) { + if (visible) { + setVisibility(View.VISIBLE); + } else { + setVisibility(View.GONE); + } + + manageChangeListener(); + } + + /** + * Manages whether this renderer is listening for caption style changes. + */ + private void manageChangeListener() { + final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE; + if (mHasChangeListener != needsListener) { + mHasChangeListener = needsListener; + + if (needsListener) { + mManager.addCaptioningChangeListener(mCaptioningListener); + + final CaptionStyle captionStyle = mManager.getUserStyle(); + final float fontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO; + setCaptionStyle(captionStyle, fontSize); + } else { + mManager.removeCaptioningChangeListener(mCaptioningListener); + } + } + } + + public void setActiveCues(Vector activeCues) { + final Context context = getContext(); + final CaptionStyle captionStyle = mCaptionStyle; + final float fontSize = mFontSize; + + prepForPrune(); + + // Ensure we have all necessary cue and region boxes. + final int count = activeCues.size(); + for (int i = 0; i < count; i++) { + final TextTrackCue cue = (TextTrackCue) activeCues.get(i); + final TextTrackRegion region = cue.mRegion; + if (region != null) { + RegionLayout regionBox = mRegionBoxes.get(region); + if (regionBox == null) { + regionBox = new RegionLayout(context, region, captionStyle, fontSize); + mRegionBoxes.put(region, regionBox); + addView(regionBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + regionBox.put(cue); + } else { + CueLayout cueBox = mCueBoxes.get(cue); + if (cueBox == null) { + cueBox = new CueLayout(context, cue, captionStyle, fontSize); + mCueBoxes.put(cue, cueBox); + addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + cueBox.update(); + cueBox.setOrder(i); + } + } + + prune(); + + // Force measurement and layout. + final int width = getWidth(); + final int height = getHeight(); + setSize(width, height); + + if (mListener != null) { + mListener.onChanged(this); + } + } + + private void setCaptionStyle(CaptionStyle captionStyle, float fontSize) { + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + cueBox.setCaptionStyle(captionStyle, fontSize); + } + + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + regionBox.setCaptionStyle(captionStyle, fontSize); + } + } + + /** + * Remove inactive cues and regions. + */ + private void prune() { + int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + if (regionBox.prune()) { + removeView(regionBox); + mRegionBoxes.removeAt(i); + regionCount--; + i--; + } + } + + int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + if (!cueBox.isActive()) { + removeView(cueBox); + mCueBoxes.removeAt(i); + cueCount--; + i--; + } + } + } + + /** + * Reset active cues and regions. + */ + private void prepForPrune() { + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + regionBox.prepForPrune(); + } + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + cueBox.prepForPrune(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + regionBox.measureForParent(widthMeasureSpec, heightMeasureSpec); + } + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + cueBox.measureForParent(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int viewportWidth = r - l; + final int viewportHeight = b - t; + + setCaptionStyle(mCaptionStyle, + mManager.getFontScale() * LINE_HEIGHT_RATIO * viewportHeight); + + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + layoutRegion(viewportWidth, viewportHeight, regionBox); + } + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + layoutCue(viewportWidth, viewportHeight, cueBox); + } + } + + /** + * Lays out a region within the viewport. The region handles layout for + * contained cues. + */ + private void layoutRegion( + int viewportWidth, int viewportHeight, + RegionLayout regionBox) { + final TextTrackRegion region = regionBox.getRegion(); + final int regionHeight = regionBox.getMeasuredHeight(); + final int regionWidth = regionBox.getMeasuredWidth(); + + // TODO: Account for region anchor point. + final float x = region.mViewportAnchorPointX; + final float y = region.mViewportAnchorPointY; + final int left = (int) (x * (viewportWidth - regionWidth) / 100); + final int top = (int) (y * (viewportHeight - regionHeight) / 100); + + regionBox.layout(left, top, left + regionWidth, top + regionHeight); + } + + /** + * Lays out a cue within the viewport. + */ + private void layoutCue( + int viewportWidth, int viewportHeight, CueLayout cueBox) { + final TextTrackCue cue = cueBox.getCue(); + final int direction = getLayoutDirection(); + final int absAlignment = resolveCueAlignment(direction, cue.mAlignment); + final boolean cueSnapToLines = cue.mSnapToLines; + + int size = 100 * cueBox.getMeasuredWidth() / viewportWidth; + + // Determine raw x-position. + int xPosition; + switch (absAlignment) { + case TextTrackCue.ALIGNMENT_LEFT: + xPosition = cue.mTextPosition; + break; + case TextTrackCue.ALIGNMENT_RIGHT: + xPosition = cue.mTextPosition - size; + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + default: + xPosition = cue.mTextPosition - size / 2; + break; + } + + // Adjust x-position for layout. + if (direction == LAYOUT_DIRECTION_RTL) { + xPosition = 100 - xPosition; + } + + // If the text track cue snap-to-lines flag is set, adjust + // x-position and size for padding. This is equivalent to placing the + // cue within the title-safe area. + if (cueSnapToLines) { + final int paddingLeft = 100 * getPaddingLeft() / viewportWidth; + final int paddingRight = 100 * getPaddingRight() / viewportWidth; + if (xPosition < paddingLeft && xPosition + size > paddingLeft) { + xPosition += paddingLeft; + size -= paddingLeft; + } + final float rightEdge = 100 - paddingRight; + if (xPosition < rightEdge && xPosition + size > rightEdge) { + size -= paddingRight; + } + } + + // Compute absolute left position and width. + final int left = xPosition * viewportWidth / 100; + final int width = size * viewportWidth / 100; + + // Determine initial y-position. + final int yPosition = calculateLinePosition(cueBox); + + // Compute absolute final top position and height. + final int height = cueBox.getMeasuredHeight(); + final int top; + if (yPosition < 0) { + // TODO: This needs to use the actual height of prior boxes. + top = viewportHeight + yPosition * height; + } else { + top = yPosition * (viewportHeight - height) / 100; + } + + // Layout cue in final position. + cueBox.layout(left, top, left + width, top + height); + } + + /** + * Calculates the line position for a cue. + *

        + * If the resulting position is negative, it represents a bottom-aligned + * position relative to the number of active cues. Otherwise, it represents + * a percentage [0-100] of the viewport height. + */ + private int calculateLinePosition(CueLayout cueBox) { + final TextTrackCue cue = cueBox.getCue(); + final Integer linePosition = cue.mLinePosition; + final boolean snapToLines = cue.mSnapToLines; + final boolean autoPosition = (linePosition == null); + + if (!snapToLines && !autoPosition && (linePosition < 0 || linePosition > 100)) { + // Invalid line position defaults to 100. + return 100; + } else if (!autoPosition) { + // Use the valid, supplied line position. + return linePosition; + } else if (!snapToLines) { + // Automatic, non-snapped line position defaults to 100. + return 100; + } else { + // Automatic snapped line position uses active cue order. + return -(cueBox.mOrder + 1); + } + } + + /** + * Resolves cue alignment according to the specified layout direction. + */ + private static int resolveCueAlignment(int layoutDirection, int alignment) { + switch (alignment) { + case TextTrackCue.ALIGNMENT_START: + return layoutDirection == View.LAYOUT_DIRECTION_LTR ? + TextTrackCue.ALIGNMENT_LEFT : TextTrackCue.ALIGNMENT_RIGHT; + case TextTrackCue.ALIGNMENT_END: + return layoutDirection == View.LAYOUT_DIRECTION_LTR ? + TextTrackCue.ALIGNMENT_RIGHT : TextTrackCue.ALIGNMENT_LEFT; + } + return alignment; + } + + private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() { + @Override + public void onFontScaleChanged(float fontScale) { + final float fontSize = fontScale * getHeight() * LINE_HEIGHT_RATIO; + setCaptionStyle(mCaptionStyle, fontSize); + } + + @Override + public void onUserStyleChanged(CaptionStyle userStyle) { + setCaptionStyle(userStyle, mFontSize); + } + }; + + /** + * A text track region represents a portion of the video viewport and + * provides a rendering area for text track cues. + */ + private static class RegionLayout extends LinearLayout { + private final ArrayList mRegionCueBoxes = new ArrayList(); + private final TextTrackRegion mRegion; + + private CaptionStyle mCaptionStyle; + private float mFontSize; + + public RegionLayout(Context context, TextTrackRegion region, CaptionStyle captionStyle, + float fontSize) { + super(context); + + mRegion = region; + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + // TODO: Add support for vertical text + setOrientation(VERTICAL); + + if (DEBUG) { + setBackgroundColor(DEBUG_REGION_BACKGROUND); + } + } + + public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) { + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + final int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + cueBox.setCaptionStyle(captionStyle, fontSize); + } + } + + /** + * Performs the parent's measurement responsibilities, then + * automatically performs its own measurement. + */ + public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) { + final TextTrackRegion region = mRegion; + final int specWidth = MeasureSpec.getSize(widthMeasureSpec); + final int specHeight = MeasureSpec.getSize(heightMeasureSpec); + final int width = (int) region.mWidth; + + // Determine the absolute maximum region size as the requested size. + final int size = width * specWidth / 100; + + widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST); + measure(widthMeasureSpec, heightMeasureSpec); + } + + /** + * Prepares this region for pruning by setting all tracks as inactive. + *

        + * Tracks that are added or updated using {@link #put(TextTrackCue)} + * after this calling this method will be marked as active. + */ + public void prepForPrune() { + final int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + cueBox.prepForPrune(); + } + } + + /** + * Adds a {@link TextTrackCue} to this region. If the track had already + * been added, updates its active state. + * + * @param cue + */ + public void put(TextTrackCue cue) { + final int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + if (cueBox.getCue() == cue) { + cueBox.update(); + return; + } + } + + final CueLayout cueBox = new CueLayout(getContext(), cue, mCaptionStyle, mFontSize); + mRegionCueBoxes.add(cueBox); + addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + if (getChildCount() > mRegion.mLines) { + removeViewAt(0); + } + } + + /** + * Remove all inactive tracks from this region. + * + * @return true if this region is empty and should be pruned + */ + public boolean prune() { + int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + if (!cueBox.isActive()) { + mRegionCueBoxes.remove(i); + removeView(cueBox); + cueCount--; + i--; + } + } + + return mRegionCueBoxes.isEmpty(); + } + + /** + * @return the region data backing this layout + */ + public TextTrackRegion getRegion() { + return mRegion; + } + } + + /** + * A text track cue is the unit of time-sensitive data in a text track, + * corresponding for instance for subtitles and captions to the text that + * appears at a particular time and disappears at another time. + *

        + * A single cue may contain multiple {@link SpanLayout}s, each representing a + * single line of text. + */ + private static class CueLayout extends LinearLayout { + public final TextTrackCue mCue; + + private CaptionStyle mCaptionStyle; + private float mFontSize; + + private boolean mActive; + private int mOrder; + + public CueLayout( + Context context, TextTrackCue cue, CaptionStyle captionStyle, float fontSize) { + super(context); + + mCue = cue; + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + // TODO: Add support for vertical text. + final boolean horizontal = cue.mWritingDirection + == TextTrackCue.WRITING_DIRECTION_HORIZONTAL; + setOrientation(horizontal ? VERTICAL : HORIZONTAL); + + switch (cue.mAlignment) { + case TextTrackCue.ALIGNMENT_END: + setGravity(Gravity.END); + break; + case TextTrackCue.ALIGNMENT_LEFT: + setGravity(Gravity.LEFT); + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + setGravity(horizontal + ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL); + break; + case TextTrackCue.ALIGNMENT_RIGHT: + setGravity(Gravity.RIGHT); + break; + case TextTrackCue.ALIGNMENT_START: + setGravity(Gravity.START); + break; + } + + if (DEBUG) { + setBackgroundColor(DEBUG_CUE_BACKGROUND); + } + + update(); + } + + public void setCaptionStyle(CaptionStyle style, float fontSize) { + mCaptionStyle = style; + mFontSize = fontSize; + + final int n = getChildCount(); + for (int i = 0; i < n; i++) { + final View child = getChildAt(i); + if (child instanceof SpanLayout) { + ((SpanLayout) child).setCaptionStyle(style, fontSize); + } + } + } + + public void prepForPrune() { + mActive = false; + } + + public void update() { + mActive = true; + + removeAllViews(); + + final int cueAlignment = resolveCueAlignment(getLayoutDirection(), mCue.mAlignment); + final Alignment alignment; + switch (cueAlignment) { + case TextTrackCue.ALIGNMENT_LEFT: + alignment = Alignment.ALIGN_LEFT; + break; + case TextTrackCue.ALIGNMENT_RIGHT: + alignment = Alignment.ALIGN_RIGHT; + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + default: + alignment = Alignment.ALIGN_CENTER; + } + + final CaptionStyle captionStyle = mCaptionStyle; + final float fontSize = mFontSize; + final TextTrackCueSpan[][] lines = mCue.mLines; + final int lineCount = lines.length; + for (int i = 0; i < lineCount; i++) { + final SpanLayout lineBox = new SpanLayout(getContext(), lines[i]); + lineBox.setAlignment(alignment); + lineBox.setCaptionStyle(captionStyle, fontSize); + + addView(lineBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + /** + * Performs the parent's measurement responsibilities, then + * automatically performs its own measurement. + */ + public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) { + final TextTrackCue cue = mCue; + final int specWidth = MeasureSpec.getSize(widthMeasureSpec); + final int specHeight = MeasureSpec.getSize(heightMeasureSpec); + final int direction = getLayoutDirection(); + final int absAlignment = resolveCueAlignment(direction, cue.mAlignment); + + // Determine the maximum size of cue based on its starting position + // and the direction in which it grows. + final int maximumSize; + switch (absAlignment) { + case TextTrackCue.ALIGNMENT_LEFT: + maximumSize = 100 - cue.mTextPosition; + break; + case TextTrackCue.ALIGNMENT_RIGHT: + maximumSize = cue.mTextPosition; + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + if (cue.mTextPosition <= 50) { + maximumSize = cue.mTextPosition * 2; + } else { + maximumSize = (100 - cue.mTextPosition) * 2; + } + break; + default: + maximumSize = 0; + } + + // Determine absolute maximum cue size as the smaller of the + // requested size and the maximum theoretical size. + final int size = Math.min(cue.mSize, maximumSize) * specWidth / 100; + widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST); + measure(widthMeasureSpec, heightMeasureSpec); + } + + /** + * Sets the order of this cue in the list of active cues. + * + * @param order the order of this cue in the list of active cues + */ + public void setOrder(int order) { + mOrder = order; + } + + /** + * @return whether this cue is marked as active + */ + public boolean isActive() { + return mActive; + } + + /** + * @return the cue data backing this layout + */ + public TextTrackCue getCue() { + return mCue; + } + } + + /** + * A text track line represents a single line of text within a cue. + *

        + * A single line may contain multiple spans, each representing a section of + * text that may be enabled or disabled at a particular time. + */ + private static class SpanLayout extends SubtitleView { + private final SpannableStringBuilder mBuilder = new SpannableStringBuilder(); + private final TextTrackCueSpan[] mSpans; + + public SpanLayout(Context context, TextTrackCueSpan[] spans) { + super(context); + + mSpans = spans; + + update(); + } + + public void update() { + final SpannableStringBuilder builder = mBuilder; + final TextTrackCueSpan[] spans = mSpans; + + builder.clear(); + builder.clearSpans(); + + final int spanCount = spans.length; + for (int i = 0; i < spanCount; i++) { + final TextTrackCueSpan span = spans[i]; + if (span.mEnabled) { + builder.append(spans[i].mText); + } + } + + setText(builder); + } + + public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) { + setBackgroundColor(captionStyle.backgroundColor); + setForegroundColor(captionStyle.foregroundColor); + setEdgeColor(captionStyle.edgeColor); + setEdgeType(captionStyle.edgeType); + setTypeface(captionStyle.getTypeface()); + setTextSize(fontSize); + } + } +} diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 52c0c2d9f5fa9d1757a921b85dd8042fd11b9f62..12f7bd9a995686bf91516c67f733f5dfca89c46e 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -119,6 +119,14 @@ public class AudioEffect { public static final UUID EFFECT_TYPE_NS = UUID .fromString("58b4b260-8e06-11e0-aa8e-0002a5d5c51b"); + /** + * @hide + * CANDIDATE FOR PUBLIC API + * UUID for Loudness Enhancer + */ + public static final UUID EFFECT_TYPE_LOUDNESS_ENHANCER = UUID + .fromString("fe3199be-aed0-413f-87bb-11260eb63cf1"); + /** * Null effect UUID. Used when the UUID for effect type of * @hide diff --git a/media/java/android/media/audiofx/LoudnessEnhancer.java b/media/java/android/media/audiofx/LoudnessEnhancer.java new file mode 100644 index 0000000000000000000000000000000000000000..eb2fb753e59c88a1d382ebae20628070e5d0ad85 --- /dev/null +++ b/media/java/android/media/audiofx/LoudnessEnhancer.java @@ -0,0 +1,291 @@ +/* + * 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.media.audiofx; + +import android.media.AudioTrack; +import android.media.MediaPlayer; +import android.media.audiofx.AudioEffect; +import android.util.Log; + +import java.util.StringTokenizer; + + +/** + * LoudnessEnhancer is an audio effect for increasing audio loudness. + * The processing is parametrized by a target gain value, which determines the maximum amount + * by which an audio signal will be amplified; signals amplified outside of the sample + * range supported by the platform are compressed. + * An application creates a LoudnessEnhancer object to instantiate and control a + * this audio effect in the audio framework. + * To attach the LoudnessEnhancer to a particular AudioTrack or MediaPlayer, + * specify the audio session ID of this AudioTrack or MediaPlayer when constructing the effect + * (see {@link AudioTrack#getAudioSessionId()} and {@link MediaPlayer#getAudioSessionId()}). + */ + +public class LoudnessEnhancer extends AudioEffect { + + private final static String TAG = "LoudnessEnhancer"; + + // These parameter constants must be synchronized with those in + // /system/media/audio_effects/include/audio_effects/effect_loudnessenhancer.h + /** + * The maximum gain applied applied to the signal to process. + * It is expressed in millibels (100mB = 1dB) where 0mB corresponds to no amplification. + */ + public static final int PARAM_TARGET_GAIN_MB = 0; + + /** + * Registered listener for parameter changes. + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change events + * from AudioEffect super class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * @hide + * Class constructor. + * @param audioSession system-wide unique audio session identifier. The LoudnessEnhancer + * will be attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public LoudnessEnhancer(int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_LOUDNESS_ENHANCER, EFFECT_TYPE_NULL, 0, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a LoudnessEnhancer to global output mix is deprecated!"); + } + } + + /** + * @hide + * Class constructor for the LoudnessEnhancer audio effect. + * @param priority the priority level requested by the application for controlling the + * LoudnessEnhancer engine. As the same engine can be shared by several applications, + * this parameter indicates how much the requesting application needs control of effect + * parameters. The normal priority is 0, above normal is a positive number, below normal a + * negative number. + * @param audioSession system-wide unique audio session identifier. The LoudnessEnhancer + * will be attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public LoudnessEnhancer(int priority, int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_LOUDNESS_ENHANCER, EFFECT_TYPE_NULL, priority, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a LoudnessEnhancer to global output mix is deprecated!"); + } + } + + /** + * Set the target gain for the audio effect. + * The target gain is the maximum value by which a sample value will be amplified when the + * effect is enabled. + * @param gainmB the effect target gain expressed in mB. 0mB corresponds to no amplification. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setTargetGain(int gainmB) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_TARGET_GAIN_MB, gainmB)); + } + + /** + * Return the target gain. + * @return the effect target gain expressed in mB. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public float getTargetGain() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] value = new int[1]; + checkStatus(getParameter(PARAM_TARGET_GAIN_MB, value)); + return value[0]; + } + + /** + * @hide + * The OnParameterChangeListener interface defines a method called by the LoudnessEnhancer + * when a parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * LoudnessEnhancer engine. + * @param effect the LoudnessEnhancer on which the interface is registered. + * @param param ID of the modified parameter. See {@link #PARAM_GENERIC_PARAM1} ... + * @param value the new parameter value. + */ + void onParameterChange(LoudnessEnhancer effect, int param, int value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + // only notify when the parameter was successfully change + if (status != AudioEffect.SUCCESS) { + return; + } + OnParameterChangeListener l = null; + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p = -1; + int v = Integer.MIN_VALUE; + + if (param.length == 4) { + p = byteArrayToInt(param, 0); + } + if (value.length == 4) { + v = byteArrayToInt(value, 0); + } + if (p != -1 && v != Integer.MIN_VALUE) { + l.onParameterChange(LoudnessEnhancer.this, p, v); + } + } + } + } + + /** + * @hide + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + mParamListener = listener; + } + } + + /** + * @hide + * The Settings class regroups the LoudnessEnhancer parameters. It is used in + * conjunction with the getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public int targetGainmB; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + //int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("LoudnessEnhancer")) { + throw new IllegalArgumentException( + "invalid settings for LoudnessEnhancer: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("targetGainmB")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + targetGainmB = Integer.parseInt(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "LoudnessEnhancer"+ + ";targetGainmB="+Integer.toString(targetGainmB) + ); + return str; + } + }; + + + /** + * @hide + * Gets the LoudnessEnhancer properties. This method is useful when a snapshot of current + * effect settings must be saved by the application. + * @return a LoudnessEnhancer.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public LoudnessEnhancer.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + int[] value = new int[1]; + checkStatus(getParameter(PARAM_TARGET_GAIN_MB, value)); + settings.targetGainmB = value[0]; + return settings; + } + + /** + * @hide + * Sets the LoudnessEnhancer properties. This method is useful when bass boost settings + * have to be applied from a previous backup. + * @param settings a LoudnessEnhancer.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(LoudnessEnhancer.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_TARGET_GAIN_MB, settings.targetGainmB)); + } +} diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index 9197ed8e02c6f6885528ce6c5dcd27ddba64f01b..fb7f7183dae970df84c24c51911c5136dac25a29 100644 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -57,6 +57,11 @@ import android.os.Message; * anymore to free up native resources associated to the Visualizer instance. *

        Creating a Visualizer on the output mix (audio session 0) requires permission * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} + *

        The Visualizer class can also be used to perform measurements on the audio being played back. + * The measurements to perform are defined by setting a mask of the requested measurement modes with + * {@link #setMeasurementMode(int)}. Supported values are {@link #MEASUREMENT_MODE_NONE} to cancel + * any measurement, and {@link #MEASUREMENT_MODE_PEAK_RMS} for peak and RMS monitoring. + * Measurements can be retrieved through {@link #getMeasurementPeakRms(MeasurementPeakRms)}. */ public class Visualizer { @@ -93,6 +98,19 @@ public class Visualizer { */ public static final int SCALING_MODE_AS_PLAYED = 1; + /** + * Defines a measurement mode in which no measurements are performed. + */ + public static final int MEASUREMENT_MODE_NONE = 0; + + /** + * Defines a measurement mode which computes the peak and RMS value in mB, where 0mB is the + * maximum sample value, and -9600mB is the minimum value. + * Values for peak and RMS can be retrieved with + * {@link #getMeasurementPeakRms(MeasurementPeakRms)}. + */ + public static final int MEASUREMENT_MODE_PEAK_RMS = 1 << 0; + // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp private static final int NATIVE_EVENT_PCM_CAPTURE = 0; private static final int NATIVE_EVENT_FFT_CAPTURE = 1; @@ -349,6 +367,43 @@ public class Visualizer { } } + /** + * Sets the combination of measurement modes to be performed by this audio effect. + * @param mode a mask of the measurements to perform. The valid values are + * {@link #MEASUREMENT_MODE_NONE} (to cancel any measurement) + * or {@link #MEASUREMENT_MODE_PEAK_RMS}. + * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE} in case of failure. + * @throws IllegalStateException + */ + public int setMeasurementMode(int mode) + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("setMeasurementMode() called in wrong state: " + + mState)); + } + return native_setMeasurementMode(mode); + } + } + + /** + * Returns the current measurement modes performed by this audio effect + * @return the mask of the measurements, + * {@link #MEASUREMENT_MODE_NONE} (when no measurements are performed) + * or {@link #MEASUREMENT_MODE_PEAK_RMS}. + * @throws IllegalStateException + */ + public int getMeasurementMode() + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("getMeasurementMode() called in wrong state: " + + mState)); + } + return native_getMeasurementMode(); + } + } + /** * Returns the sampling rate of the captured audio. * @return the sampling rate in milliHertz. @@ -437,6 +492,46 @@ public class Visualizer { } } + /** + * A class to store peak and RMS values. + * Peak and RMS are expressed in mB, as described in the + * {@link Visualizer#MEASUREMENT_MODE_PEAK_RMS} measurement mode. + */ + public static final class MeasurementPeakRms { + /** + * The peak value in mB. + */ + public int mPeak; + /** + * The RMS value in mB. + */ + public int mRms; + } + + /** + * Retrieves the latest peak and RMS measurement. + * Sets the peak and RMS fields of the supplied {@link Visualizer.MeasurementPeakRms} to the + * latest measured values. + * @param measurement a non-null {@link Visualizer.MeasurementPeakRms} instance to store + * the measurement values. + * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE}, + * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} + * in case of failure. + */ + public int getMeasurementPeakRms(MeasurementPeakRms measurement) { + if (measurement == null) { + Log.e(TAG, "Cannot store measurements in a null object"); + return ERROR_BAD_VALUE; + } + synchronized (mStateLock) { + if (mState != STATE_ENABLED) { + throw (new IllegalStateException("getMeasurementPeakRms() called in wrong state: " + + mState)); + } + return native_getPeakRms(measurement); + } + } + //--------------------------------------------------------- // Interface definitions //-------------------- @@ -640,12 +735,18 @@ public class Visualizer { private native final int native_getScalingMode(); + private native final int native_setMeasurementMode(int mode); + + private native final int native_getMeasurementMode(); + private native final int native_getSamplingRate(); private native final int native_getWaveForm(byte[] waveform); private native final int native_getFft(byte[] fft); + private native final int native_getPeakRms(MeasurementPeakRms measurement); + private native final int native_setPeriodicCapture(int rate, boolean waveForm, boolean fft); //--------------------------------------------------------- diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 416a2a17d5bb8a30127a9d1e07ceb3e4f9372b57..63a61e2edf229feb41562e85b582f6f6e1775130 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + android_media_ImageReader.cpp \ android_media_MediaCrypto.cpp \ android_media_MediaCodec.cpp \ android_media_MediaCodecList.cpp \ @@ -56,6 +57,8 @@ LOCAL_C_INCLUDES += \ frameworks/av/media/libstagefright/codecs/amrnb/common/include \ frameworks/av/media/mtp \ frameworks/native/include/media/openmax \ + $(call include-path-for, libhardware)/hardware \ + system/media/camera/include \ $(PV_INCLUDES) \ $(JNI_H_INCLUDE) \ $(call include-path-for, corecg graphics) diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0030dbdafee2cb0a73c97e29cb450694f4be1856 --- /dev/null +++ b/media/jni/android_media_ImageReader.cpp @@ -0,0 +1,879 @@ +/* + * Copyright 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ImageReader_JNI" +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) ) + +#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext" +#define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID "mLockedBuffer" +#define ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID "mTimestamp" + +// ---------------------------------------------------------------------------- + +using namespace android; + +enum { + IMAGE_READER_MAX_NUM_PLANES = 3, +}; + +enum { + ACQUIRE_SUCCESS = 0, + ACQUIRE_NO_BUFFERS = 1, + ACQUIRE_MAX_IMAGES = 2, +}; + +static struct { + jfieldID mNativeContext; + jmethodID postEventFromNative; +} gImageReaderClassInfo; + +static struct { + jfieldID mLockedBuffer; + jfieldID mTimestamp; +} gSurfaceImageClassInfo; + +static struct { + jclass clazz; + jmethodID ctor; +} gSurfacePlaneClassInfo; + +// ---------------------------------------------------------------------------- + +class JNIImageReaderContext : public CpuConsumer::FrameAvailableListener +{ +public: + JNIImageReaderContext(JNIEnv* env, jobject weakThiz, jclass clazz, int maxImages); + + virtual ~JNIImageReaderContext(); + + virtual void onFrameAvailable(); + + CpuConsumer::LockedBuffer* getLockedBuffer(); + + void returnLockedBuffer(CpuConsumer::LockedBuffer* buffer); + + void setCpuConsumer(const sp& consumer) { mConsumer = consumer; } + CpuConsumer* getCpuConsumer() { return mConsumer.get(); } + + void setBufferQueue(const sp& bq) { mBufferQueue = bq; } + BufferQueue* getBufferQueue() { return mBufferQueue.get(); } + + void setBufferFormat(int format) { mFormat = format; } + int getBufferFormat() { return mFormat; } + + void setBufferWidth(int width) { mWidth = width; } + int getBufferWidth() { return mWidth; } + + void setBufferHeight(int height) { mHeight = height; } + int getBufferHeight() { return mHeight; } + +private: + static JNIEnv* getJNIEnv(bool* needsDetach); + static void detachJNI(); + + List mBuffers; + sp mConsumer; + sp mBufferQueue; + jobject mWeakThiz; + jclass mClazz; + int mFormat; + int mWidth; + int mHeight; +}; + +JNIImageReaderContext::JNIImageReaderContext(JNIEnv* env, + jobject weakThiz, jclass clazz, int maxImages) : + mWeakThiz(env->NewGlobalRef(weakThiz)), + mClazz((jclass)env->NewGlobalRef(clazz)) { + for (int i = 0; i < maxImages; i++) { + CpuConsumer::LockedBuffer *buffer = new CpuConsumer::LockedBuffer; + mBuffers.push_back(buffer); + } +} + +JNIEnv* JNIImageReaderContext::getJNIEnv(bool* needsDetach) { + LOG_ALWAYS_FATAL_IF(needsDetach == NULL, "needsDetach is null!!!"); + *needsDetach = false; + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL}; + JavaVM* vm = AndroidRuntime::getJavaVM(); + int result = vm->AttachCurrentThread(&env, (void*) &args); + if (result != JNI_OK) { + ALOGE("thread attach failed: %#x", result); + return NULL; + } + *needsDetach = true; + } + return env; +} + +void JNIImageReaderContext::detachJNI() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + int result = vm->DetachCurrentThread(); + if (result != JNI_OK) { + ALOGE("thread detach failed: %#x", result); + } +} + +CpuConsumer::LockedBuffer* JNIImageReaderContext::getLockedBuffer() { + if (mBuffers.empty()) { + return NULL; + } + // Return a LockedBuffer pointer and remove it from the list + List::iterator it = mBuffers.begin(); + CpuConsumer::LockedBuffer* buffer = *it; + mBuffers.erase(it); + return buffer; +} + +void JNIImageReaderContext::returnLockedBuffer(CpuConsumer::LockedBuffer* buffer) { + mBuffers.push_back(buffer); +} + +JNIImageReaderContext::~JNIImageReaderContext() { + bool needsDetach = false; + JNIEnv* env = getJNIEnv(&needsDetach); + if (env != NULL) { + env->DeleteGlobalRef(mWeakThiz); + env->DeleteGlobalRef(mClazz); + } else { + ALOGW("leaking JNI object references"); + } + if (needsDetach) { + detachJNI(); + } + + // Delete LockedBuffers + for (List::iterator it = mBuffers.begin(); + it != mBuffers.end(); it++) { + delete *it; + } + mBuffers.clear(); + mConsumer.clear(); +} + +void JNIImageReaderContext::onFrameAvailable() +{ + ALOGV("%s: frame available", __FUNCTION__); + bool needsDetach = false; + JNIEnv* env = getJNIEnv(&needsDetach); + if (env != NULL) { + env->CallStaticVoidMethod(mClazz, gImageReaderClassInfo.postEventFromNative, mWeakThiz); + } else { + ALOGW("onFrameAvailable event will not posted"); + } + if (needsDetach) { + detachJNI(); + } +} + +// ---------------------------------------------------------------------------- + +extern "C" { + +static JNIImageReaderContext* ImageReader_getContext(JNIEnv* env, jobject thiz) +{ + JNIImageReaderContext *ctx; + ctx = reinterpret_cast + (env->GetLongField(thiz, gImageReaderClassInfo.mNativeContext)); + return ctx; +} + +static CpuConsumer* ImageReader_getCpuConsumer(JNIEnv* env, jobject thiz) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + jniThrowRuntimeException(env, "ImageReaderContext is not initialized"); + return NULL; + } + return ctx->getCpuConsumer(); +} + +static BufferQueue* ImageReader_getBufferQueue(JNIEnv* env, jobject thiz) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + jniThrowRuntimeException(env, "ImageReaderContext is not initialized"); + return NULL; + } + return ctx->getBufferQueue(); +} + +static void ImageReader_setNativeContext(JNIEnv* env, + jobject thiz, sp ctx) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* const p = ImageReader_getContext(env, thiz); + if (ctx != 0) { + ctx->incStrong((void*)ImageReader_setNativeContext); + } + if (p) { + p->decStrong((void*)ImageReader_setNativeContext); + } + env->SetLongField(thiz, gImageReaderClassInfo.mNativeContext, + reinterpret_cast(ctx.get())); +} + +static CpuConsumer::LockedBuffer* Image_getLockedBuffer(JNIEnv* env, jobject image) +{ + return reinterpret_cast( + env->GetLongField(image, gSurfaceImageClassInfo.mLockedBuffer)); +} + +static void Image_setBuffer(JNIEnv* env, jobject thiz, + const CpuConsumer::LockedBuffer* buffer) +{ + env->SetLongField(thiz, gSurfaceImageClassInfo.mLockedBuffer, reinterpret_cast(buffer)); +} + +// Some formats like JPEG defined with different values between android.graphics.ImageFormat and +// graphics.h, need convert to the one defined in graphics.h here. +static int Image_getPixelFormat(JNIEnv* env, int format) +{ + int jpegFormat, rawSensorFormat; + jfieldID fid; + + ALOGV("%s: format = 0x%x", __FUNCTION__, format); + + jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat"); + ALOG_ASSERT(imageFormatClazz != NULL); + + fid = env->GetStaticFieldID(imageFormatClazz, "JPEG", "I"); + jpegFormat = env->GetStaticIntField(imageFormatClazz, fid); + fid = env->GetStaticFieldID(imageFormatClazz, "RAW_SENSOR", "I"); + rawSensorFormat = env->GetStaticIntField(imageFormatClazz, fid); + + // Translate the JPEG to BLOB for camera purpose, an add more if more mismatch is found. + if (format == jpegFormat) { + format = HAL_PIXEL_FORMAT_BLOB; + } + // Same thing for RAW_SENSOR format + if (format == rawSensorFormat) { + format = HAL_PIXEL_FORMAT_RAW_SENSOR; + } + + return format; +} + +static uint32_t Image_getJpegSize(CpuConsumer::LockedBuffer* buffer) +{ + ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!"); + uint32_t size = 0; + uint32_t width = buffer->width; + uint8_t* jpegBuffer = buffer->data; + + // First check for JPEG transport header at the end of the buffer + uint8_t* header = jpegBuffer + (width - sizeof(struct camera3_jpeg_blob)); + struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header); + if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID) { + size = blob->jpeg_size; + ALOGV("%s: Jpeg size = %d", __FUNCTION__, size); + } + + // failed to find size, default to whole buffer + if (size == 0) { + size = width; + } + + return size; +} + +static void Image_getLockedBufferInfo(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx, + uint8_t **base, uint32_t *size) +{ + ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!"); + ALOG_ASSERT(base != NULL, "base is NULL!!!"); + ALOG_ASSERT(size != NULL, "size is NULL!!!"); + ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0)); + + ALOGV("%s: buffer: %p", __FUNCTION__, buffer); + + uint32_t dataSize, ySize, cSize, cStride; + uint8_t *cb, *cr; + uint8_t *pData = NULL; + int bytesPerPixel = 0; + + dataSize = ySize = cSize = cStride = 0; + int32_t fmt = buffer->format; + switch (fmt) { + case HAL_PIXEL_FORMAT_YCbCr_420_888: + pData = + (idx == 0) ? + buffer->data : + (idx == 1) ? + buffer->dataCb : + buffer->dataCr; + if (idx == 0) { + dataSize = buffer->stride * buffer->height; + } else { + dataSize = buffer->chromaStride * buffer->height / 2; + } + break; + // NV21 + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + cr = buffer->data + (buffer->stride * buffer->height); + cb = cr + 1; + ySize = buffer->width * buffer->height; + cSize = buffer->width * buffer->height / 2; + + pData = + (idx == 0) ? + buffer->data : + (idx == 1) ? + cb: + cr; + + dataSize = (idx == 0) ? ySize : cSize; + break; + case HAL_PIXEL_FORMAT_YV12: + // Y and C stride need to be 16 pixel aligned. + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + + ySize = buffer->stride * buffer->height; + cStride = ALIGN(buffer->stride / 2, 16); + cr = buffer->data + ySize; + cSize = cStride * buffer->height / 2; + cb = cr + cSize; + + pData = + (idx == 0) ? + buffer->data : + (idx == 1) ? + cb : + cr; + dataSize = (idx == 0) ? ySize : cSize; + break; + case HAL_PIXEL_FORMAT_Y8: + // Single plane, 8bpp. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + + pData = buffer->data; + dataSize = buffer->stride * buffer->height; + break; + case HAL_PIXEL_FORMAT_Y16: + // Single plane, 16bpp, strides are specified in pixels, not in bytes + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + + pData = buffer->data; + dataSize = buffer->stride * buffer->height * 2; + break; + case HAL_PIXEL_FORMAT_BLOB: + // Used for JPEG data, height must be 1, width == size, single plane. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + ALOG_ASSERT(buffer->height == 1, "JPEG should has height value %d", buffer->height); + + pData = buffer->data; + dataSize = Image_getJpegSize(buffer); + break; + case HAL_PIXEL_FORMAT_RAW_SENSOR: + // Single plane 16bpp bayer data. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->width * 2 * buffer->height; + break; + case HAL_PIXEL_FORMAT_RGBA_8888: + case HAL_PIXEL_FORMAT_RGBX_8888: + // Single plane, 32bpp. + bytesPerPixel = 4; + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->stride * buffer->height * bytesPerPixel; + break; + case HAL_PIXEL_FORMAT_RGB_565: + // Single plane, 16bpp. + bytesPerPixel = 2; + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->stride * buffer->height * bytesPerPixel; + break; + case HAL_PIXEL_FORMAT_RGB_888: + // Single plane, 24bpp. + bytesPerPixel = 3; + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->stride * buffer->height * bytesPerPixel; + break; + default: + jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException", + "Pixel format: 0x%x is unsupported", fmt); + break; + } + + *base = pData; + *size = dataSize; +} + +static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx) +{ + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0), "Index is out of range:%d", idx); + + int pixelStride = 0; + ALOG_ASSERT(buffer != NULL, "buffer is NULL"); + + int32_t fmt = buffer->format; + switch (fmt) { + case HAL_PIXEL_FORMAT_YCbCr_420_888: + pixelStride = (idx == 0) ? 1 : buffer->chromaStep; + break; + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + pixelStride = (idx == 0) ? 1 : 2; + break; + case HAL_PIXEL_FORMAT_Y8: + // Single plane 8bpp data. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride; + break; + case HAL_PIXEL_FORMAT_YV12: + pixelStride = 1; + break; + case HAL_PIXEL_FORMAT_BLOB: + // Used for JPEG data, single plane, row and pixel strides are 0 + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 0; + break; + case HAL_PIXEL_FORMAT_Y16: + case HAL_PIXEL_FORMAT_RAW_SENSOR: + case HAL_PIXEL_FORMAT_RGB_565: + // Single plane 16bpp data. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 2; + break; + case HAL_PIXEL_FORMAT_RGBA_8888: + case HAL_PIXEL_FORMAT_RGBX_8888: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 4; + break; + case HAL_PIXEL_FORMAT_RGB_888: + // Single plane, 24bpp. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 3; + break; + default: + jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException", + "Pixel format: 0x%x is unsupported", fmt); + break; + } + + return pixelStride; +} + +static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx) +{ + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0)); + + int rowStride = 0; + ALOG_ASSERT(buffer != NULL, "buffer is NULL"); + + int32_t fmt = buffer->format; + + switch (fmt) { + case HAL_PIXEL_FORMAT_YCbCr_420_888: + rowStride = (idx == 0) ? buffer->stride : buffer->chromaStride; + break; + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + rowStride = buffer->width; + break; + case HAL_PIXEL_FORMAT_YV12: + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + rowStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16); + break; + case HAL_PIXEL_FORMAT_BLOB: + // Used for JPEG data, single plane, row and pixel strides are 0 + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = 0; + break; + case HAL_PIXEL_FORMAT_Y8: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + rowStride = buffer->stride; + break; + case HAL_PIXEL_FORMAT_Y16: + case HAL_PIXEL_FORMAT_RAW_SENSOR: + // In native side, strides are specified in pixels, not in bytes. + // Single plane 16bpp bayer data. even width/height, + // row stride multiple of 16 pixels (32 bytes) + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + rowStride = buffer->stride * 2; + break; + case HAL_PIXEL_FORMAT_RGB_565: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = buffer->stride * 2; + break; + case HAL_PIXEL_FORMAT_RGBA_8888: + case HAL_PIXEL_FORMAT_RGBX_8888: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = buffer->stride * 4; + break; + case HAL_PIXEL_FORMAT_RGB_888: + // Single plane, 24bpp. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = buffer->stride * 3; + break; + default: + ALOGE("%s Pixel format: 0x%x is unsupported", __FUNCTION__, fmt); + jniThrowException(env, "java/lang/UnsupportedOperationException", + "unsupported buffer format"); + break; + } + + return rowStride; +} + +// ---------------------------------------------------------------------------- + +static void ImageReader_classInit(JNIEnv* env, jclass clazz) +{ + ALOGV("%s:", __FUNCTION__); + + jclass imageClazz = env->FindClass("android/media/ImageReader$SurfaceImage"); + LOG_ALWAYS_FATAL_IF(imageClazz == NULL, + "can't find android/graphics/ImageReader$SurfaceImage"); + gSurfaceImageClassInfo.mLockedBuffer = env->GetFieldID( + imageClazz, ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mLockedBuffer == NULL, + "can't find android/graphics/ImageReader.%s", + ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID); + + gSurfaceImageClassInfo.mTimestamp = env->GetFieldID( + imageClazz, ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mTimestamp == NULL, + "can't find android/graphics/ImageReader.%s", + ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID); + + gImageReaderClassInfo.mNativeContext = env->GetFieldID( + clazz, ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gImageReaderClassInfo.mNativeContext == NULL, + "can't find android/graphics/ImageReader.%s", + ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID); + + gImageReaderClassInfo.postEventFromNative = env->GetStaticMethodID( + clazz, "postEventFromNative", "(Ljava/lang/Object;)V"); + LOG_ALWAYS_FATAL_IF(gImageReaderClassInfo.postEventFromNative == NULL, + "can't find android/graphics/ImageReader.postEventFromNative"); + + jclass planeClazz = env->FindClass("android/media/ImageReader$SurfaceImage$SurfacePlane"); + LOG_ALWAYS_FATAL_IF(planeClazz == NULL, "Can not find SurfacePlane class"); + // FindClass only gives a local reference of jclass object. + gSurfacePlaneClassInfo.clazz = (jclass) env->NewGlobalRef(planeClazz); + gSurfacePlaneClassInfo.ctor = env->GetMethodID(gSurfacePlaneClassInfo.clazz, "", + "(Landroid/media/ImageReader$SurfaceImage;III)V"); + LOG_ALWAYS_FATAL_IF(gSurfacePlaneClassInfo.ctor == NULL, + "Can not find SurfacePlane constructor"); +} + +static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, + jint width, jint height, jint format, jint maxImages) +{ + status_t res; + int nativeFormat; + + ALOGV("%s: width:%d, height: %d, format: 0x%x, maxImages:%d", + __FUNCTION__, width, height, format, maxImages); + + nativeFormat = Image_getPixelFormat(env, format); + + sp bq = new BufferQueue(); + sp consumer = new CpuConsumer(bq, maxImages, + /*controlledByApp*/true); + // TODO: throw dvm exOutOfMemoryError? + if (consumer == NULL) { + jniThrowRuntimeException(env, "Failed to allocate native CpuConsumer"); + return; + } + + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + jniThrowRuntimeException(env, "Can't find android/graphics/ImageReader"); + return; + } + sp ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages)); + ctx->setCpuConsumer(consumer); + ctx->setBufferQueue(bq); + consumer->setFrameAvailableListener(ctx); + ImageReader_setNativeContext(env, thiz, ctx); + ctx->setBufferFormat(nativeFormat); + ctx->setBufferWidth(width); + ctx->setBufferHeight(height); + + // Set the width/height/format to the CpuConsumer + res = consumer->setDefaultBufferSize(width, height); + if (res != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set CpuConsumer buffer size"); + return; + } + res = consumer->setDefaultBufferFormat(nativeFormat); + if (res != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set CpuConsumer buffer format"); + } +} + +static void ImageReader_close(JNIEnv* env, jobject thiz) +{ + ALOGV("%s:", __FUNCTION__); + + JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + // ImageReader is already closed. + return; + } + + CpuConsumer* consumer = ImageReader_getCpuConsumer(env, thiz); + if (consumer != NULL) { + consumer->abandon(); + consumer->setFrameAvailableListener(NULL); + } + ImageReader_setNativeContext(env, thiz, NULL); +} + +static void ImageReader_imageRelease(JNIEnv* env, jobject thiz, jobject image) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + ALOGW("ImageReader#close called before Image#close, consider calling Image#close first"); + return; + } + + CpuConsumer* consumer = ctx->getCpuConsumer(); + CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, image); + if (!buffer) { + ALOGW("Image already released!!!"); + return; + } + consumer->unlockBuffer(*buffer); + Image_setBuffer(env, image, NULL); + ctx->returnLockedBuffer(buffer); +} + +static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, + jobject image) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + jniThrowRuntimeException(env, "ImageReaderContext is not initialized"); + return -1; + } + + CpuConsumer* consumer = ctx->getCpuConsumer(); + CpuConsumer::LockedBuffer* buffer = ctx->getLockedBuffer(); + if (buffer == NULL) { + ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than" + " maxImages buffers"); + return ACQUIRE_MAX_IMAGES; + } + status_t res = consumer->lockNextBuffer(buffer); + if (res != NO_ERROR) { + if (res != BAD_VALUE /*no buffers*/) { + if (res == NOT_ENOUGH_DATA) { + return ACQUIRE_MAX_IMAGES; + } else { + ALOGE("%s Fail to lockNextBuffer with error: %d ", + __FUNCTION__, res); + jniThrowExceptionFmt(env, "java/lang/AssertionError", + "Unknown error (%d) when we tried to lock buffer.", + res); + } + } + return ACQUIRE_NO_BUFFERS; + } + + if (buffer->format == HAL_PIXEL_FORMAT_YCrCb_420_SP) { + jniThrowException(env, "java/lang/UnsupportedOperationException", + "NV21 format is not supported by ImageReader"); + return -1; + } + + // Check if the left-top corner of the crop rect is origin, we currently assume this point is + // zero, will revist this once this assumption turns out problematic. + Point lt = buffer->crop.leftTop(); + if (lt.x != 0 || lt.y != 0) { + jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException", + "crop left top corner [%d, %d] need to be at origin", lt.x, lt.y); + return -1; + } + + // Check if the producer buffer configurations match what ImageReader configured. + // We want to fail for the very first image because this case is too bad. + int outputWidth = buffer->width; + int outputHeight = buffer->height; + + // Correct width/height when crop is set. + if (!buffer->crop.isEmpty()) { + outputWidth = buffer->crop.getWidth(); + outputHeight = buffer->crop.getHeight(); + } + + int imageReaderWidth = ctx->getBufferWidth(); + int imageReaderHeight = ctx->getBufferHeight(); + if ((buffer->format != HAL_PIXEL_FORMAT_BLOB) && + (imageReaderWidth != outputWidth || imageReaderHeight > outputHeight)) { + /** + * For video decoder, the buffer height is actually the vertical stride, + * which is always >= actual image height. For future, decoder need provide + * right crop rectangle to CpuConsumer to indicate the actual image height, + * see bug 9563986. After this bug is fixed, we can enforce the height equal + * check. Right now, only make sure buffer height is no less than ImageReader + * height. + */ + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d", + outputWidth, outputHeight, imageReaderWidth, imageReaderHeight); + return -1; + } + + if (ctx->getBufferFormat() != buffer->format) { + // Return the buffer to the queue. + consumer->unlockBuffer(*buffer); + ctx->returnLockedBuffer(buffer); + + // Throw exception + ALOGE("Producer output buffer format: 0x%x, ImageReader configured format: 0x%x", + buffer->format, ctx->getBufferFormat()); + String8 msg; + msg.appendFormat("The producer output buffer format 0x%x doesn't " + "match the ImageReader's configured buffer format 0x%x.", + buffer->format, ctx->getBufferFormat()); + jniThrowException(env, "java/lang/UnsupportedOperationException", + msg.string()); + return -1; + } + // Set SurfaceImage instance member variables + Image_setBuffer(env, image, buffer); + env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp, + static_cast(buffer->timestamp)); + + return ACQUIRE_SUCCESS; +} + +static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz) +{ + ALOGV("%s: ", __FUNCTION__); + + BufferQueue* bq = ImageReader_getBufferQueue(env, thiz); + if (bq == NULL) { + jniThrowRuntimeException(env, "CpuConsumer is uninitialized"); + return NULL; + } + + // Wrap the IGBP in a Java-language Surface. + return android_view_Surface_createFromIGraphicBufferProducer(env, bq); +} + +static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx) +{ + int rowStride, pixelStride; + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + + CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz); + + ALOG_ASSERT(buffer != NULL); + if (buffer == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "Image was released"); + } + rowStride = Image_imageGetRowStride(env, buffer, idx); + pixelStride = Image_imageGetPixelStride(env, buffer, idx); + + jobject surfPlaneObj = env->NewObject(gSurfacePlaneClassInfo.clazz, + gSurfacePlaneClassInfo.ctor, thiz, idx, rowStride, pixelStride); + + return surfPlaneObj; +} + +static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx) +{ + uint8_t *base = NULL; + uint32_t size = 0; + jobject byteBuffer; + + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + + CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz); + + if (buffer == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "Image was released"); + } + + // Create byteBuffer from native buffer + Image_getLockedBufferInfo(env, buffer, idx, &base, &size); + byteBuffer = env->NewDirectByteBuffer(base, size); + // TODO: throw dvm exOutOfMemoryError? + if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) { + jniThrowException(env, "java/lang/IllegalStateException", "Failed to allocate ByteBuffer"); + } + + return byteBuffer; +} + +} // extern "C" + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gImageReaderMethods[] = { + {"nativeClassInit", "()V", (void*)ImageReader_classInit }, + {"nativeInit", "(Ljava/lang/Object;IIII)V", (void*)ImageReader_init }, + {"nativeClose", "()V", (void*)ImageReader_close }, + {"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease }, + {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup }, + {"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface }, +}; + +static JNINativeMethod gImageMethods[] = { + {"nativeImageGetBuffer", "(I)Ljava/nio/ByteBuffer;", (void*)Image_getByteBuffer }, + {"nativeCreatePlane", "(I)Landroid/media/ImageReader$SurfaceImage$SurfacePlane;", + (void*)Image_createSurfacePlane }, +}; + +int register_android_media_ImageReader(JNIEnv *env) { + + int ret1 = AndroidRuntime::registerNativeMethods(env, + "android/media/ImageReader", gImageReaderMethods, NELEM(gImageReaderMethods)); + + int ret2 = AndroidRuntime::registerNativeMethods(env, + "android/media/ImageReader$SurfaceImage", gImageMethods, NELEM(gImageMethods)); + + return (ret1 || ret2); +} diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index cd1d9ce85635f66c10878f2627158b6abf15795c..b8d437cc97b6c352c53f5f702f7a657456576463 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -38,6 +38,8 @@ #include #include +#include + #include namespace android { @@ -49,9 +51,14 @@ enum { DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED = -3, }; +struct CryptoErrorCodes { + jint cryptoErrorNoKey; + jint cryptoErrorKeyExpired; + jint cryptoErrorResourceBusy; +} gCryptoErrorCodes; + struct fields_t { jfieldID context; - jfieldID cryptoInfoNumSubSamplesID; jfieldID cryptoInfoNumBytesOfClearDataID; jfieldID cryptoInfoNumBytesOfEncryptedDataID; @@ -81,7 +88,7 @@ JMediaCodec::JMediaCodec( mLooper->start( false, // runOnCallingThread false, // canCallJava - PRIORITY_DEFAULT); + PRIORITY_FOREGROUND); if (nameIsType) { mCodec = MediaCodec::CreateByType(mLooper, name, encoder); @@ -115,7 +122,7 @@ status_t JMediaCodec::configure( int flags) { sp client; if (bufferProducer != NULL) { - mSurfaceTextureClient = new Surface(bufferProducer); + mSurfaceTextureClient = new Surface(bufferProducer, true /* controlledByApp */); } else { mSurfaceTextureClient.clear(); } @@ -181,9 +188,10 @@ status_t JMediaCodec::dequeueOutputBuffer( return err; } - jclass clazz = env->FindClass("android/media/MediaCodec$BufferInfo"); + ScopedLocalRef clazz( + env, env->FindClass("android/media/MediaCodec$BufferInfo")); - jmethodID method = env->GetMethodID(clazz, "set", "(IIJI)V"); + jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V"); env->CallVoidMethod(bufferInfo, method, offset, size, timeUs, flags); return OK; @@ -222,29 +230,33 @@ status_t JMediaCodec::getBuffers( return err; } - jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer"); - CHECK(byteBufferClass != NULL); + ScopedLocalRef byteBufferClass( + env, env->FindClass("java/nio/ByteBuffer")); + + CHECK(byteBufferClass.get() != NULL); jmethodID orderID = env->GetMethodID( - byteBufferClass, + byteBufferClass.get(), "order", "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); CHECK(orderID != NULL); - jclass byteOrderClass = env->FindClass("java/nio/ByteOrder"); - CHECK(byteOrderClass != NULL); + ScopedLocalRef byteOrderClass( + env, env->FindClass("java/nio/ByteOrder")); + + CHECK(byteOrderClass.get() != NULL); jmethodID nativeOrderID = env->GetStaticMethodID( - byteOrderClass, "nativeOrder", "()Ljava/nio/ByteOrder;"); + byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); CHECK(nativeOrderID != NULL); jobject nativeByteOrderObj = - env->CallStaticObjectMethod(byteOrderClass, nativeOrderID); + env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID); CHECK(nativeByteOrderObj != NULL); *bufArray = (jobjectArray)env->NewObjectArray( - buffers.size(), byteBufferClass, NULL); + buffers.size(), byteBufferClass.get(), NULL); if (*bufArray == NULL) { env->DeleteLocalRef(nativeByteOrderObj); return NO_MEMORY; @@ -298,6 +310,10 @@ status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { return OK; } +status_t JMediaCodec::setParameters(const sp &msg) { + return mCodec->setParameters(msg); +} + void JMediaCodec::setVideoScalingMode(int mode) { if (mSurfaceTextureClient != NULL) { native_window_set_scaling_mode(mSurfaceTextureClient.get(), mode); @@ -333,26 +349,41 @@ static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) { } static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) { - jclass clazz = env->FindClass("android/media/MediaCodec$CryptoException"); - CHECK(clazz != NULL); + ScopedLocalRef clazz( + env, env->FindClass("android/media/MediaCodec$CryptoException")); + CHECK(clazz.get() != NULL); jmethodID constructID = - env->GetMethodID(clazz, "", "(ILjava/lang/String;)V"); + env->GetMethodID(clazz.get(), "", "(ILjava/lang/String;)V"); CHECK(constructID != NULL); jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error"); + /* translate OS errors to Java API CryptoException errorCodes */ + switch (err) { + case ERROR_DRM_NO_LICENSE: + err = gCryptoErrorCodes.cryptoErrorNoKey; + break; + case ERROR_DRM_LICENSE_EXPIRED: + err = gCryptoErrorCodes.cryptoErrorKeyExpired; + break; + case ERROR_DRM_RESOURCE_BUSY: + err = gCryptoErrorCodes.cryptoErrorResourceBusy; + break; + default: + break; + } + jthrowable exception = - (jthrowable)env->NewObject(clazz, constructID, err, msgObj); + (jthrowable)env->NewObject(clazz.get(), constructID, err, msgObj); env->Throw(exception); } static jint throwExceptionAsNecessary( JNIEnv *env, status_t err, const char *msg = NULL) { - if (err >= ERROR_DRM_WV_VENDOR_MIN && err <= ERROR_DRM_WV_VENDOR_MAX) { + if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) { // We'll throw our custom MediaCodec.CryptoException - throwCryptoException(env, err, msg); return 0; } @@ -370,6 +401,12 @@ static jint throwExceptionAsNecessary( case INFO_OUTPUT_BUFFERS_CHANGED: return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED; + case ERROR_DRM_NO_LICENSE: + case ERROR_DRM_LICENSE_EXPIRED: + case ERROR_DRM_RESOURCE_BUSY: + throwCryptoException(env, err, msg); + break; + default: { jniThrowException(env, "java/lang/IllegalStateException", msg); @@ -804,6 +841,27 @@ static jobject android_media_MediaCodec_getName( return NULL; } +static void android_media_MediaCodec_setParameters( + JNIEnv *env, jobject thiz, jobjectArray keys, jobjectArray vals) { + ALOGV("android_media_MediaCodec_setParameters"); + + sp codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + sp params; + status_t err = ConvertKeyValueArraysToMessage(env, keys, vals, ¶ms); + + if (err == OK) { + err = codec->setParameters(params); + } + + throwExceptionAsNecessary(env, err); +} + static void android_media_MediaCodec_setVideoScalingMode( JNIEnv *env, jobject thiz, jint mode) { sp codec = getMediaCodec(env, thiz); @@ -823,35 +881,55 @@ static void android_media_MediaCodec_setVideoScalingMode( } static void android_media_MediaCodec_native_init(JNIEnv *env) { - jclass clazz = env->FindClass("android/media/MediaCodec"); - CHECK(clazz != NULL); + ScopedLocalRef clazz( + env, env->FindClass("android/media/MediaCodec")); + CHECK(clazz.get() != NULL); - gFields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "I"); CHECK(gFields.context != NULL); - clazz = env->FindClass("android/media/MediaCodec$CryptoInfo"); - CHECK(clazz != NULL); + clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo")); + CHECK(clazz.get() != NULL); gFields.cryptoInfoNumSubSamplesID = - env->GetFieldID(clazz, "numSubSamples", "I"); + env->GetFieldID(clazz.get(), "numSubSamples", "I"); CHECK(gFields.cryptoInfoNumSubSamplesID != NULL); gFields.cryptoInfoNumBytesOfClearDataID = - env->GetFieldID(clazz, "numBytesOfClearData", "[I"); + env->GetFieldID(clazz.get(), "numBytesOfClearData", "[I"); CHECK(gFields.cryptoInfoNumBytesOfClearDataID != NULL); gFields.cryptoInfoNumBytesOfEncryptedDataID = - env->GetFieldID(clazz, "numBytesOfEncryptedData", "[I"); + env->GetFieldID(clazz.get(), "numBytesOfEncryptedData", "[I"); CHECK(gFields.cryptoInfoNumBytesOfEncryptedDataID != NULL); - gFields.cryptoInfoKeyID = env->GetFieldID(clazz, "key", "[B"); + gFields.cryptoInfoKeyID = env->GetFieldID(clazz.get(), "key", "[B"); CHECK(gFields.cryptoInfoKeyID != NULL); - gFields.cryptoInfoIVID = env->GetFieldID(clazz, "iv", "[B"); + gFields.cryptoInfoIVID = env->GetFieldID(clazz.get(), "iv", "[B"); CHECK(gFields.cryptoInfoIVID != NULL); - gFields.cryptoInfoModeID = env->GetFieldID(clazz, "mode", "I"); + gFields.cryptoInfoModeID = env->GetFieldID(clazz.get(), "mode", "I"); CHECK(gFields.cryptoInfoModeID != NULL); + + clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException")); + CHECK(clazz.get() != NULL); + + jfieldID field; + field = env->GetStaticFieldID(clazz.get(), "ERROR_NO_KEY", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorNoKey = + env->GetStaticIntField(clazz.get(), field); + + field = env->GetStaticFieldID(clazz.get(), "ERROR_KEY_EXPIRED", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorKeyExpired = + env->GetStaticIntField(clazz.get(), field); + + field = env->GetStaticFieldID(clazz.get(), "ERROR_RESOURCE_BUSY", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorResourceBusy = + env->GetStaticIntField(clazz.get(), field); } static void android_media_MediaCodec_native_setup( @@ -933,6 +1011,9 @@ static JNINativeMethod gMethods[] = { { "getName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, + { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V", + (void *)android_media_MediaCodec_setParameters }, + { "setVideoScalingMode", "(I)V", (void *)android_media_MediaCodec_setVideoScalingMode }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 282d2c58840554e39c0be1a388db7d1df5ac53d8..2fbbd7236462d92831a9f16e6528014c237fbcb3 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -87,6 +87,8 @@ struct JMediaCodec : public RefBase { status_t getName(JNIEnv *env, jstring *name) const; + status_t setParameters(const sp ¶ms); + void setVideoScalingMode(int mode); protected: diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp index 04430ec26609cff8bfc988a064dcdcf4999f9ec3..caa594e654b7725c5d0bcaf86d85b2b0d0361f48 100644 --- a/media/jni/android_media_MediaCodecList.cpp +++ b/media/jni/android_media_MediaCodecList.cpp @@ -110,10 +110,11 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( Vector profileLevels; Vector colorFormats; + uint32_t flags; status_t err = MediaCodecList::getInstance()->getCodecCapabilities( - index, typeStr, &profileLevels, &colorFormats); + index, typeStr, &profileLevels, &colorFormats, &flags); env->ReleaseStringUTFChars(type, typeStr); typeStr = NULL; @@ -127,6 +128,9 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( env->FindClass("android/media/MediaCodecInfo$CodecCapabilities"); CHECK(capsClazz != NULL); + jfieldID flagsField = + env->GetFieldID(capsClazz, "flags", "I"); + jobject caps = env->AllocObject(capsClazz); jclass profileLevelClazz = @@ -163,6 +167,8 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( env->SetObjectField(caps, profileLevelsField, profileLevelArray); + env->SetIntField(caps, flagsField, flags); + env->DeleteLocalRef(profileLevelArray); profileLevelArray = NULL; diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 7799ca4dc76e43a07c81c9d58eca3f176ca2f3dd..bbb74d25bed1a65b0113002ec507cc5bd4fe5cdb 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -21,6 +21,7 @@ #include "android_media_MediaDrm.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "android_os_Parcel.h" #include "jni.h" #include "JNIHelp.h" @@ -242,6 +243,9 @@ static bool throwExceptionAsNecessary( } else if (err == ERROR_DRM_NOT_PROVISIONED) { jniThrowException(env, "android/media/NotProvisionedException", msg); return true; + } else if (err == ERROR_DRM_RESOURCE_BUSY) { + jniThrowException(env, "android/media/ResourceBusyException", msg); + return true; } else if (err == ERROR_DRM_DEVICE_REVOKED) { jniThrowException(env, "android/media/DeniedByServerException", msg); return true; @@ -345,14 +349,14 @@ void JDrm::notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj) // static -bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16]) { +bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) { sp drm = MakeDrm(); if (drm == NULL) { return false; } - return drm->isCryptoSchemeSupported(uuid); + return drm->isCryptoSchemeSupported(uuid, mimeType); } status_t JDrm::initCheck() const { @@ -608,7 +612,7 @@ static void android_media_MediaDrm_native_finalize( } static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( - JNIEnv *env, jobject thiz, jbyteArray uuidObj) { + JNIEnv *env, jobject thiz, jbyteArray uuidObj, jstring jmimeType) { if (uuidObj == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); @@ -625,7 +629,12 @@ static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( return false; } - return JDrm::IsCryptoSchemeSupported(uuid.array()); + String8 mimeType; + if (jmimeType != NULL) { + mimeType = JStringToString8(env, jmimeType); + } + + return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType); } static jbyteArray android_media_MediaDrm_openSession( @@ -750,7 +759,9 @@ static jbyteArray android_media_MediaDrm_provideKeyResponse( status_t err = drm->provideKeyResponse(sessionId, response, keySetId); - throwExceptionAsNecessary(env, err, "Failed to handle key response"); + if (throwExceptionAsNecessary(env, err, "Failed to handle key response")) { + return NULL; + } return VectorToJByteArray(env, keySetId); } @@ -1101,7 +1112,9 @@ static jbyteArray android_media_MediaDrm_encryptNative( status_t err = drm->encrypt(sessionId, keyId, input, iv, output); - throwExceptionAsNecessary(env, err, "Failed to encrypt"); + if (throwExceptionAsNecessary(env, err, "Failed to encrypt")) { + return NULL; + } return VectorToJByteArray(env, output); } @@ -1129,7 +1142,9 @@ static jbyteArray android_media_MediaDrm_decryptNative( Vector output; status_t err = drm->decrypt(sessionId, keyId, input, iv, output); - throwExceptionAsNecessary(env, err, "Failed to decrypt"); + if (throwExceptionAsNecessary(env, err, "Failed to decrypt")) { + return NULL; + } return VectorToJByteArray(env, output); } @@ -1157,7 +1172,9 @@ static jbyteArray android_media_MediaDrm_signNative( status_t err = drm->sign(sessionId, keyId, message, signature); - throwExceptionAsNecessary(env, err, "Failed to sign"); + if (throwExceptionAsNecessary(env, err, "Failed to sign")) { + return NULL; + } return VectorToJByteArray(env, signature); } @@ -1201,7 +1218,7 @@ static JNINativeMethod gMethods[] = { { "native_finalize", "()V", (void *)android_media_MediaDrm_native_finalize }, - { "isCryptoSchemeSupportedNative", "([B)Z", + { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z", (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, { "openSession", "()[B", diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h index 9b3917fd849df06b68d319c2e05877a1b7aae5e0..620ad2832babff56fd395bd03d11e03a228e77ef 100644 --- a/media/jni/android_media_MediaDrm.h +++ b/media/jni/android_media_MediaDrm.h @@ -37,7 +37,7 @@ public: }; struct JDrm : public BnDrmClient { - static bool IsCryptoSchemeSupported(const uint8_t uuid[16]); + static bool IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType); JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16]); diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 1704d5c17bf2b1f462c41d16f383a9f219e5a2ce..1ac45d4452e3d550c47d1582f42ac3b420576d96 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -22,6 +22,7 @@ #include "android_media_Utils.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "jni.h" #include "JNIHelp.h" diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp index 7517e8561a20f5d6d419f686fec5a2cfc7ab5426..457b956e3745e1a98caf1a04d50b20f44fb2de0c 100644 --- a/media/jni/android_media_MediaMuxer.cpp +++ b/media/jni/android_media_MediaMuxer.cpp @@ -164,6 +164,18 @@ static void android_media_MediaMuxer_setOrientationHint( } +static void android_media_MediaMuxer_setLocation( + JNIEnv *env, jclass clazz, jint nativeObject, jint latitude, jint longitude) { + MediaMuxer* muxer = reinterpret_cast(nativeObject); + + status_t res = muxer->setLocation(latitude, longitude); + if (res != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set location"); + return; + } +} + static void android_media_MediaMuxer_start(JNIEnv *env, jclass clazz, jint nativeObject) { sp muxer(reinterpret_cast(nativeObject)); @@ -216,6 +228,9 @@ static JNINativeMethod gMethods[] = { { "nativeSetOrientationHint", "(II)V", (void *)android_media_MediaMuxer_setOrientationHint}, + { "nativeSetLocation", "(III)V", + (void *)android_media_MediaMuxer_setLocation}, + { "nativeStart", "(I)V", (void *)android_media_MediaMuxer_start}, { "nativeWriteSampleData", "(IILjava/nio/ByteBuffer;IIJI)V", diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index d06380d73272317c25748107e11d8b903f24a703..4be9cd611deddd855863ef29458c726f33131b69 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -31,6 +31,7 @@ #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" +#include "android_runtime/Log.h" #include "utils/Errors.h" // for status_t #include "utils/KeyedVector.h" #include "utils/String8.h" @@ -526,14 +527,6 @@ android_media_MediaPlayer_setVolume(JNIEnv *env, jobject thiz, float leftVolume, process_media_player_call( env, thiz, mp->setVolume(leftVolume, rightVolume), NULL, NULL ); } -// FIXME: deprecated -static jobject -android_media_MediaPlayer_getFrameAt(JNIEnv *env, jobject thiz, jint msec) -{ - return NULL; -} - - // Sends the request and reply parcels to the media player via the // binder interface. static jint @@ -782,39 +775,6 @@ android_media_MediaPlayer_setRetransmitEndpoint(JNIEnv *env, jobject thiz, return ret; } -static jboolean -android_media_MediaPlayer_setParameter(JNIEnv *env, jobject thiz, jint key, jobject java_request) -{ - ALOGV("setParameter: key %d", key); - sp mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return false; - } - - Parcel *request = parcelForJavaObject(env, java_request); - status_t err = mp->setParameter(key, *request); - if (err == OK) { - return true; - } else { - return false; - } -} - -static void -android_media_MediaPlayer_getParameter(JNIEnv *env, jobject thiz, jint key, jobject java_reply) -{ - ALOGV("getParameter: key %d", key); - sp mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return; - } - - Parcel *reply = parcelForJavaObject(env, java_reply); - process_media_player_call(env, thiz, mp->getParameter(key, reply), NULL, NULL ); -} - static void android_media_MediaPlayer_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject java_player) { @@ -913,7 +873,6 @@ static JNINativeMethod gMethods[] = { {"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping}, {"isLooping", "()Z", (void *)android_media_MediaPlayer_isLooping}, {"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume}, - {"getFrameAt", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaPlayer_getFrameAt}, {"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke}, {"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_setMetadataFilter}, {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_getMetadata}, @@ -925,8 +884,6 @@ static JNINativeMethod gMethods[] = { {"setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer_setAuxEffectSendLevel}, {"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer_attachAuxEffect}, {"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData}, - {"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_setParameter}, - {"getParameter", "(ILandroid/os/Parcel;)V", (void *)android_media_MediaPlayer_getParameter}, {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint}, {"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer}, {"updateProxyConfig", "(Landroid/net/ProxyProperties;)V", (void *)android_media_MediaPlayer_updateProxyConfig}, @@ -941,6 +898,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } +extern int register_android_media_ImageReader(JNIEnv *env); extern int register_android_media_Crypto(JNIEnv *env); extern int register_android_media_Drm(JNIEnv *env); extern int register_android_media_MediaCodec(JNIEnv *env); @@ -968,6 +926,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) } assert(env != NULL); + if (register_android_media_ImageReader(env) < 0) { + ALOGE("ERROR: ImageReader native registration failed"); + goto bail; + } + if (register_android_media_MediaPlayer(env) < 0) { ALOGE("ERROR: MediaPlayer native registration failed\n"); goto bail; diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp index 5d27966f491a6c5fff1926cd9ffddfd6ff113b63..4e3d14e338109d227431463f2d8bc811d3850633 100644 --- a/media/jni/android_media_MediaScanner.cpp +++ b/media/jni/android_media_MediaScanner.cpp @@ -25,6 +25,7 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" using namespace android; diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp index e35ace38829eb4a5af369c76826cb2dab3632292..54c5e9bdd267b4a301d7f8ff81b8f394ab473f8c 100644 --- a/media/jni/android_media_Utils.cpp +++ b/media/jni/android_media_Utils.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace android { bool ConvertKeyValueArraysToKeyedVector( @@ -76,33 +78,35 @@ bool ConvertKeyValueArraysToKeyedVector( } static jobject makeIntegerObject(JNIEnv *env, int32_t value) { - jclass clazz = env->FindClass("java/lang/Integer"); - CHECK(clazz != NULL); + ScopedLocalRef clazz(env, env->FindClass("java/lang/Integer")); + CHECK(clazz.get() != NULL); - jmethodID integerConstructID = env->GetMethodID(clazz, "", "(I)V"); + jmethodID integerConstructID = + env->GetMethodID(clazz.get(), "", "(I)V"); CHECK(integerConstructID != NULL); - return env->NewObject(clazz, integerConstructID, value); + return env->NewObject(clazz.get(), integerConstructID, value); } static jobject makeLongObject(JNIEnv *env, int64_t value) { - jclass clazz = env->FindClass("java/lang/Long"); - CHECK(clazz != NULL); + ScopedLocalRef clazz(env, env->FindClass("java/lang/Long")); + CHECK(clazz.get() != NULL); - jmethodID longConstructID = env->GetMethodID(clazz, "", "(J)V"); + jmethodID longConstructID = env->GetMethodID(clazz.get(), "", "(J)V"); CHECK(longConstructID != NULL); - return env->NewObject(clazz, longConstructID, value); + return env->NewObject(clazz.get(), longConstructID, value); } static jobject makeFloatObject(JNIEnv *env, float value) { - jclass clazz = env->FindClass("java/lang/Float"); - CHECK(clazz != NULL); + ScopedLocalRef clazz(env, env->FindClass("java/lang/Float")); + CHECK(clazz.get() != NULL); - jmethodID floatConstructID = env->GetMethodID(clazz, "", "(F)V"); + jmethodID floatConstructID = + env->GetMethodID(clazz.get(), "", "(F)V"); CHECK(floatConstructID != NULL); - return env->NewObject(clazz, floatConstructID, value); + return env->NewObject(clazz.get(), floatConstructID, value); } static jobject makeByteBufferObject( @@ -110,15 +114,16 @@ static jobject makeByteBufferObject( jbyteArray byteArrayObj = env->NewByteArray(size); env->SetByteArrayRegion(byteArrayObj, 0, size, (const jbyte *)data); - jclass clazz = env->FindClass("java/nio/ByteBuffer"); - CHECK(clazz != NULL); + ScopedLocalRef clazz(env, env->FindClass("java/nio/ByteBuffer")); + CHECK(clazz.get() != NULL); jmethodID byteBufWrapID = - env->GetStaticMethodID(clazz, "wrap", "([B)Ljava/nio/ByteBuffer;"); + env->GetStaticMethodID( + clazz.get(), "wrap", "([B)Ljava/nio/ByteBuffer;"); CHECK(byteBufWrapID != NULL); jobject byteBufObj = env->CallStaticObjectMethod( - clazz, byteBufWrapID, byteArrayObj); + clazz.get(), byteBufWrapID, byteArrayObj); env->DeleteLocalRef(byteArrayObj); byteArrayObj = NULL; @@ -140,14 +145,15 @@ static void SetMapInt32( status_t ConvertMessageToMap( JNIEnv *env, const sp &msg, jobject *map) { - jclass hashMapClazz = env->FindClass("java/util/HashMap"); + ScopedLocalRef hashMapClazz( + env, env->FindClass("java/util/HashMap")); - if (hashMapClazz == NULL) { + if (hashMapClazz.get() == NULL) { return -EINVAL; } jmethodID hashMapConstructID = - env->GetMethodID(hashMapClazz, "", "()V"); + env->GetMethodID(hashMapClazz.get(), "", "()V"); if (hashMapConstructID == NULL) { return -EINVAL; @@ -155,7 +161,7 @@ status_t ConvertMessageToMap( jmethodID hashMapPutID = env->GetMethodID( - hashMapClazz, + hashMapClazz.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); @@ -163,7 +169,7 @@ status_t ConvertMessageToMap( return -EINVAL; } - jobject hashMap = env->NewObject(hashMapClazz, hashMapConstructID); + jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID); for (size_t i = 0; i < msg->countEntries(); ++i) { AMessage::Type valueType; @@ -276,17 +282,16 @@ status_t ConvertMessageToMap( status_t ConvertKeyValueArraysToMessage( JNIEnv *env, jobjectArray keys, jobjectArray values, sp *out) { - jclass stringClass = env->FindClass("java/lang/String"); - CHECK(stringClass != NULL); - - jclass integerClass = env->FindClass("java/lang/Integer"); - CHECK(integerClass != NULL); - - jclass floatClass = env->FindClass("java/lang/Float"); - CHECK(floatClass != NULL); - - jclass byteBufClass = env->FindClass("java/nio/ByteBuffer"); - CHECK(byteBufClass != NULL); + ScopedLocalRef stringClass(env, env->FindClass("java/lang/String")); + CHECK(stringClass.get() != NULL); + ScopedLocalRef integerClass(env, env->FindClass("java/lang/Integer")); + CHECK(integerClass.get() != NULL); + ScopedLocalRef longClass(env, env->FindClass("java/lang/Long")); + CHECK(longClass.get() != NULL); + ScopedLocalRef floatClass(env, env->FindClass("java/lang/Float")); + CHECK(floatClass.get() != NULL); + ScopedLocalRef byteBufClass(env, env->FindClass("java/nio/ByteBuffer")); + CHECK(byteBufClass.get() != NULL); sp msg = new AMessage; @@ -309,7 +314,7 @@ status_t ConvertKeyValueArraysToMessage( for (jsize i = 0; i < numEntries; ++i) { jobject keyObj = env->GetObjectArrayElement(keys, i); - if (!env->IsInstanceOf(keyObj, stringClass)) { + if (!env->IsInstanceOf(keyObj, stringClass.get())) { return -EINVAL; } @@ -326,7 +331,7 @@ status_t ConvertKeyValueArraysToMessage( jobject valueObj = env->GetObjectArrayElement(values, i); - if (env->IsInstanceOf(valueObj, stringClass)) { + if (env->IsInstanceOf(valueObj, stringClass.get())) { const char *value = env->GetStringUTFChars((jstring)valueObj, NULL); if (value == NULL) { @@ -337,29 +342,37 @@ status_t ConvertKeyValueArraysToMessage( env->ReleaseStringUTFChars((jstring)valueObj, value); value = NULL; - } else if (env->IsInstanceOf(valueObj, integerClass)) { + } else if (env->IsInstanceOf(valueObj, integerClass.get())) { jmethodID intValueID = - env->GetMethodID(integerClass, "intValue", "()I"); + env->GetMethodID(integerClass.get(), "intValue", "()I"); CHECK(intValueID != NULL); jint value = env->CallIntMethod(valueObj, intValueID); msg->setInt32(key.c_str(), value); - } else if (env->IsInstanceOf(valueObj, floatClass)) { + } else if (env->IsInstanceOf(valueObj, longClass.get())) { + jmethodID longValueID = + env->GetMethodID(longClass.get(), "longValue", "()J"); + CHECK(longValueID != NULL); + + jlong value = env->CallLongMethod(valueObj, longValueID); + + msg->setInt64(key.c_str(), value); + } else if (env->IsInstanceOf(valueObj, floatClass.get())) { jmethodID floatValueID = - env->GetMethodID(floatClass, "floatValue", "()F"); + env->GetMethodID(floatClass.get(), "floatValue", "()F"); CHECK(floatValueID != NULL); jfloat value = env->CallFloatMethod(valueObj, floatValueID); msg->setFloat(key.c_str(), value); - } else if (env->IsInstanceOf(valueObj, byteBufClass)) { + } else if (env->IsInstanceOf(valueObj, byteBufClass.get())) { jmethodID positionID = - env->GetMethodID(byteBufClass, "position", "()I"); + env->GetMethodID(byteBufClass.get(), "position", "()I"); CHECK(positionID != NULL); jmethodID limitID = - env->GetMethodID(byteBufClass, "limit", "()I"); + env->GetMethodID(byteBufClass.get(), "limit", "()I"); CHECK(limitID != NULL); jint position = env->CallIntMethod(valueObj, positionID); @@ -375,7 +388,7 @@ status_t ConvertKeyValueArraysToMessage( buffer->size()); } else { jmethodID arrayID = - env->GetMethodID(byteBufClass, "array", "()[B"); + env->GetMethodID(byteBufClass.get(), "array", "()[B"); CHECK(arrayID != NULL); jbyteArray byteArray = diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index fbd5d21c226fd837d36718aee8f9e199ba6da91b..77c796652442814ebae36361b2a7adcb5d5aa075 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -26,6 +26,7 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "MtpDatabase.h" #include "MtpDataPacket.h" diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp index 113784eca9cecee99c52fd793f324510aba2f2c6..b61b66c76c87592349120f440479a67ef84c3210 100644 --- a/media/jni/android_mtp_MtpDevice.cpp +++ b/media/jni/android_mtp_MtpDevice.cpp @@ -28,6 +28,7 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "private/android_filesystem_config.h" #include "MtpTypes.h" diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index 4d77cfd02087d6159da475b857ff1670bb482986..40cd06b2828326e66f734937a430241afddb4ca8 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -43,6 +43,8 @@ using namespace android; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/audiofx/Visualizer"; +static const char* const kClassPeakRmsPathName = + "android/media/audiofx/Visualizer$MeasurementPeakRms"; struct fields_t { // these fields provide access from C++ to the... @@ -50,6 +52,8 @@ struct fields_t { jmethodID midPostNativeEvent; // event post callback method jfieldID fidNativeVisualizer; // stores in Java the native Visualizer object jfieldID fidJniData; // stores in Java additional resources used by the native Visualizer + jfieldID fidPeak; // to access Visualizer.MeasurementPeakRms.mPeak + jfieldID fidRms; // to access Visualizer.MeasurementPeakRms.mRms }; static fields_t fields; @@ -257,6 +261,14 @@ android_media_visualizer_native_init(JNIEnv *env) fields.clazzEffect = (jclass)env->NewGlobalRef(clazz); + // Get the Visualizer.MeasurementPeakRms class + clazz = env->FindClass(kClassPeakRmsPathName); + if (clazz == NULL) { + ALOGE("Can't find %s", kClassPeakRmsPathName); + return; + } + jclass clazzMeasurementPeakRms = (jclass)env->NewGlobalRef(clazz); + // Get the postEvent method fields.midPostNativeEvent = env->GetStaticMethodID( fields.clazzEffect, @@ -283,7 +295,24 @@ android_media_visualizer_native_init(JNIEnv *env) ALOGE("Can't find Visualizer.%s", "mJniData"); return; } + // fidPeak + fields.fidPeak = env->GetFieldID( + clazzMeasurementPeakRms, + "mPeak", "I"); + if (fields.fidPeak == NULL) { + ALOGE("Can't find Visualizer.MeasurementPeakRms.%s", "mPeak"); + return; + } + // fidRms + fields.fidRms = env->GetFieldID( + clazzMeasurementPeakRms, + "mRms", "I"); + if (fields.fidRms == NULL) { + ALOGE("Can't find Visualizer.MeasurementPeakRms.%s", "mPeak"); + return; + } + env->DeleteGlobalRef(clazzMeasurementPeakRms); } static void android_media_visualizer_effect_callback(int32_t event, @@ -512,6 +541,26 @@ android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz) return lpVisualizer->getScalingMode(); } +static jint +android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, jint mode) +{ + Visualizer* lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == NULL) { + return VISUALIZER_ERROR_NO_INIT; + } + return translateError(lpVisualizer->setMeasurementMode(mode)); +} + +static jint +android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz) +{ + Visualizer* lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == NULL) { + return MEASUREMENT_MODE_NONE; + } + return lpVisualizer->getMeasurementMode(); +} + static jint android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz) { @@ -559,6 +608,25 @@ android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFf return status; } +static jint +android_media_visualizer_native_getPeakRms(JNIEnv *env, jobject thiz, jobject jPeakRmsObj) +{ + Visualizer* lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == NULL) { + return VISUALIZER_ERROR_NO_INIT; + } + int32_t measurements[2]; + jint status = translateError( + lpVisualizer->getIntMeasurements(MEASUREMENT_MODE_PEAK_RMS, + 2, measurements)); + if (status == VISUALIZER_SUCCESS) { + // measurement worked, write the values to the java object + env->SetIntField(jPeakRmsObj, fields.fidPeak, measurements[MEASUREMENT_IDX_PEAK]); + env->SetIntField(jPeakRmsObj, fields.fidRms, measurements[MEASUREMENT_IDX_RMS]); + } + return status; +} + static jint android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean jWaveform, jboolean jFft) { @@ -606,9 +674,13 @@ static JNINativeMethod gMethods[] = { {"native_getCaptureSize", "()I", (void *)android_media_visualizer_native_getCaptureSize}, {"native_setScalingMode", "(I)I", (void *)android_media_visualizer_native_setScalingMode}, {"native_getScalingMode", "()I", (void *)android_media_visualizer_native_getScalingMode}, + {"native_setMeasurementMode","(I)I", (void *)android_media_visualizer_native_setMeasurementMode}, + {"native_getMeasurementMode","()I", (void *)android_media_visualizer_native_getMeasurementMode}, {"native_getSamplingRate", "()I", (void *)android_media_visualizer_native_getSamplingRate}, {"native_getWaveForm", "([B)I", (void *)android_media_visualizer_native_getWaveForm}, {"native_getFft", "([B)I", (void *)android_media_visualizer_native_getFft}, + {"native_getPeakRms", "(Landroid/media/audiofx/Visualizer$MeasurementPeakRms;)I", + (void *)android_media_visualizer_native_getPeakRms}, {"native_setPeriodicCapture","(IZZ)I",(void *)android_media_setPeriodicCapture}, }; diff --git a/media/jni/mediaeditor/VideoEditorClasses.cpp b/media/jni/mediaeditor/VideoEditorClasses.cpp index 4982a472571baabe2c2ae58ab231c5c523ba2c5d..d8099dd2fd07a2cd47dbb403a6bb230501c4cfe9 100644 --- a/media/jni/mediaeditor/VideoEditorClasses.cpp +++ b/media/jni/mediaeditor/VideoEditorClasses.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorClasses" #include #include diff --git a/media/jni/mediaeditor/VideoEditorJava.cpp b/media/jni/mediaeditor/VideoEditorJava.cpp index bcf9099081ceb82e6fcec3d1bb7d991a45639f55..fde0fb57696f4cbf3057b95bf7ee81a1156cf3c3 100644 --- a/media/jni/mediaeditor/VideoEditorJava.cpp +++ b/media/jni/mediaeditor/VideoEditorJava.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorJava" + #include #include #include diff --git a/media/jni/mediaeditor/VideoEditorLogging.h b/media/jni/mediaeditor/VideoEditorLogging.h index 479d8b6f98470305b1f7cf6ea343209b2ff4a28a..1f1228a682a41fadbb51a51a35f48285e6daf80b 100644 --- a/media/jni/mediaeditor/VideoEditorLogging.h +++ b/media/jni/mediaeditor/VideoEditorLogging.h @@ -17,6 +17,16 @@ #ifndef VIDEO_EDITOR_LOGGING_H #define VIDEO_EDITOR_LOGGING_H +#ifndef LOG_TAG +#error "No LOG_TAG defined!" +#endif + +/* + * This file is used as a proxy for cutils/log.h. Include cutils/log.h here to + * avoid relying on import ordering. + */ +#include + //#define VIDEOEDIT_LOGGING_ENABLED #define VIDEOEDIT_LOG_INDENTATION (3) diff --git a/media/jni/mediaeditor/VideoEditorOsal.cpp b/media/jni/mediaeditor/VideoEditorOsal.cpp index a8c08ac936bd9ce3002124ceac8eacd3699d3ea6..c12b1f55d3e7d8b66f28fcdf42e299a4611eae7c 100644 --- a/media/jni/mediaeditor/VideoEditorOsal.cpp +++ b/media/jni/mediaeditor/VideoEditorOsal.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorOsal" + #include #include #include diff --git a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp index c8fb26336e272feb67b58cc79632525950c7bfed..2f8e357b8a3f8705c6df1e0a2ed10db914e35d90 100644 --- a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp +++ b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorPropertiesMain" + #include #include #include diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk index 5835b9f92c672ae7df06cd04a66786609d8955b2..ed8d7c10f370623275f05ea410a87d777bb62f16 100644 --- a/media/jni/soundpool/Android.mk +++ b/media/jni/soundpool/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - android_media_SoundPool.cpp + android_media_SoundPool_SoundPoolImpl.cpp LOCAL_SHARED_LIBRARIES := \ liblog \ diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp deleted file mode 100644 index 9658856b729631e87f92b31243836f17a2a470ce..0000000000000000000000000000000000000000 --- a/media/jni/soundpool/android_media_SoundPool.cpp +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2008 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. - */ - -#include - -//#define LOG_NDEBUG 0 -#define LOG_TAG "SoundPool-JNI" - -#include -#include -#include -#include -#include - -using namespace android; - -static struct fields_t { - jfieldID mNativeContext; - jmethodID mPostEvent; - jclass mSoundPoolClass; -} fields; - -static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) { - return (SoundPool*)env->GetIntField(thiz, fields.mNativeContext); -} - -// ---------------------------------------------------------------------------- -static int -android_media_SoundPool_load_URL(JNIEnv *env, jobject thiz, jstring path, jint priority) -{ - ALOGV("android_media_SoundPool_load_URL"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (path == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return 0; - } - const char* s = env->GetStringUTFChars(path, NULL); - int id = ap->load(s, priority); - env->ReleaseStringUTFChars(path, s); - return id; -} - -static int -android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor, - jlong offset, jlong length, jint priority) -{ - ALOGV("android_media_SoundPool_load_FD"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return 0; - return ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor), - int64_t(offset), int64_t(length), int(priority)); -} - -static bool -android_media_SoundPool_unload(JNIEnv *env, jobject thiz, jint sampleID) { - ALOGV("android_media_SoundPool_unload\n"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return 0; - return ap->unload(sampleID); -} - -static int -android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID, - jfloat leftVolume, jfloat rightVolume, jint priority, jint loop, - jfloat rate) -{ - ALOGV("android_media_SoundPool_play\n"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return 0; - return ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate); -} - -static void -android_media_SoundPool_pause(JNIEnv *env, jobject thiz, jint channelID) -{ - ALOGV("android_media_SoundPool_pause"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->pause(channelID); -} - -static void -android_media_SoundPool_resume(JNIEnv *env, jobject thiz, jint channelID) -{ - ALOGV("android_media_SoundPool_resume"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->resume(channelID); -} - -static void -android_media_SoundPool_autoPause(JNIEnv *env, jobject thiz) -{ - ALOGV("android_media_SoundPool_autoPause"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->autoPause(); -} - -static void -android_media_SoundPool_autoResume(JNIEnv *env, jobject thiz) -{ - ALOGV("android_media_SoundPool_autoResume"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->autoResume(); -} - -static void -android_media_SoundPool_stop(JNIEnv *env, jobject thiz, jint channelID) -{ - ALOGV("android_media_SoundPool_stop"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->stop(channelID); -} - -static void -android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID, - float leftVolume, float rightVolume) -{ - ALOGV("android_media_SoundPool_setVolume"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->setVolume(channelID, leftVolume, rightVolume); -} - -static void -android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID, - int priority) -{ - ALOGV("android_media_SoundPool_setPriority"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->setPriority(channelID, priority); -} - -static void -android_media_SoundPool_setLoop(JNIEnv *env, jobject thiz, jint channelID, - int loop) -{ - ALOGV("android_media_SoundPool_setLoop"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->setLoop(channelID, loop); -} - -static void -android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID, - float rate) -{ - ALOGV("android_media_SoundPool_setRate"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; - ap->setRate(channelID, rate); -} - -static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user) -{ - ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user); - JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->CallStaticVoidMethod(fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2, NULL); -} - -static jint -android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, jint maxChannels, jint streamType, jint srcQuality) -{ - ALOGV("android_media_SoundPool_native_setup"); - SoundPool *ap = new SoundPool(maxChannels, (audio_stream_type_t) streamType, srcQuality); - if (ap == NULL) { - return -1; - } - - // save pointer to SoundPool C++ object in opaque field in Java object - env->SetIntField(thiz, fields.mNativeContext, (int)ap); - - // set callback with weak reference - jobject globalWeakRef = env->NewGlobalRef(weakRef); - ap->setCallback(android_media_callback, globalWeakRef); - return 0; -} - -static void -android_media_SoundPool_release(JNIEnv *env, jobject thiz) -{ - ALOGV("android_media_SoundPool_release"); - SoundPool *ap = MusterSoundPool(env, thiz); - if (ap != NULL) { - - // release weak reference - jobject weakRef = (jobject) ap->getUserData(); - if (weakRef != NULL) { - env->DeleteGlobalRef(weakRef); - } - - // clear callback and native context - ap->setCallback(NULL, NULL); - env->SetIntField(thiz, fields.mNativeContext, 0); - delete ap; - } -} - -// ---------------------------------------------------------------------------- - -// Dalvik VM type signatures -static JNINativeMethod gMethods[] = { - { "_load", - "(Ljava/lang/String;I)I", - (void *)android_media_SoundPool_load_URL - }, - { "_load", - "(Ljava/io/FileDescriptor;JJI)I", - (void *)android_media_SoundPool_load_FD - }, - { "unload", - "(I)Z", - (void *)android_media_SoundPool_unload - }, - { "play", - "(IFFIIF)I", - (void *)android_media_SoundPool_play - }, - { "pause", - "(I)V", - (void *)android_media_SoundPool_pause - }, - { "resume", - "(I)V", - (void *)android_media_SoundPool_resume - }, - { "autoPause", - "()V", - (void *)android_media_SoundPool_autoPause - }, - { "autoResume", - "()V", - (void *)android_media_SoundPool_autoResume - }, - { "stop", - "(I)V", - (void *)android_media_SoundPool_stop - }, - { "setVolume", - "(IFF)V", - (void *)android_media_SoundPool_setVolume - }, - { "setPriority", - "(II)V", - (void *)android_media_SoundPool_setPriority - }, - { "setLoop", - "(II)V", - (void *)android_media_SoundPool_setLoop - }, - { "setRate", - "(IF)V", - (void *)android_media_SoundPool_setRate - }, - { "native_setup", - "(Ljava/lang/Object;III)I", - (void*)android_media_SoundPool_native_setup - }, - { "release", - "()V", - (void*)android_media_SoundPool_release - } -}; - -static const char* const kClassPathName = "android/media/SoundPool"; - -jint JNI_OnLoad(JavaVM* vm, void* reserved) -{ - JNIEnv* env = NULL; - jint result = -1; - jclass clazz; - - if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { - ALOGE("ERROR: GetEnv failed\n"); - goto bail; - } - assert(env != NULL); - - clazz = env->FindClass(kClassPathName); - if (clazz == NULL) { - ALOGE("Can't find %s", kClassPathName); - goto bail; - } - - fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "I"); - if (fields.mNativeContext == NULL) { - ALOGE("Can't find SoundPool.mNativeContext"); - goto bail; - } - - fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative", - "(Ljava/lang/Object;IIILjava/lang/Object;)V"); - if (fields.mPostEvent == NULL) { - ALOGE("Can't find android/media/SoundPool.postEventFromNative"); - goto bail; - } - - // create a reference to class. Technically, we're leaking this reference - // since it's a static object. - fields.mSoundPoolClass = (jclass) env->NewGlobalRef(clazz); - - if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0) - goto bail; - - /* success -- return valid version number */ - result = JNI_VERSION_1_4; - -bail: - return result; -} diff --git a/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..260485063f62f83be99bc92727649dd3f49e8058 --- /dev/null +++ b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2008 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. + */ + +#include + +//#define LOG_NDEBUG 0 +#define LOG_TAG "SoundPool-JNI" + +#include +#include +#include +#include +#include + +using namespace android; + +static struct fields_t { + jfieldID mNativeContext; + jmethodID mPostEvent; + jclass mSoundPoolClass; +} fields; + +static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) { + return (SoundPool*)env->GetIntField(thiz, fields.mNativeContext); +} + +// ---------------------------------------------------------------------------- +static int +android_media_SoundPool_SoundPoolImpl_load_URL(JNIEnv *env, jobject thiz, jstring path, jint priority) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_load_URL"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (path == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return 0; + } + const char* s = env->GetStringUTFChars(path, NULL); + int id = ap->load(s, priority); + env->ReleaseStringUTFChars(path, s); + return id; +} + +static int +android_media_SoundPool_SoundPoolImpl_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor, + jlong offset, jlong length, jint priority) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_load_FD"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return 0; + return ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor), + int64_t(offset), int64_t(length), int(priority)); +} + +static bool +android_media_SoundPool_SoundPoolImpl_unload(JNIEnv *env, jobject thiz, jint sampleID) { + ALOGV("android_media_SoundPool_SoundPoolImpl_unload\n"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return 0; + return ap->unload(sampleID); +} + +static int +android_media_SoundPool_SoundPoolImpl_play(JNIEnv *env, jobject thiz, jint sampleID, + jfloat leftVolume, jfloat rightVolume, jint priority, jint loop, + jfloat rate) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_play\n"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return 0; + return ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate); +} + +static void +android_media_SoundPool_SoundPoolImpl_pause(JNIEnv *env, jobject thiz, jint channelID) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_pause"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->pause(channelID); +} + +static void +android_media_SoundPool_SoundPoolImpl_resume(JNIEnv *env, jobject thiz, jint channelID) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_resume"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->resume(channelID); +} + +static void +android_media_SoundPool_SoundPoolImpl_autoPause(JNIEnv *env, jobject thiz) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_autoPause"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->autoPause(); +} + +static void +android_media_SoundPool_SoundPoolImpl_autoResume(JNIEnv *env, jobject thiz) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_autoResume"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->autoResume(); +} + +static void +android_media_SoundPool_SoundPoolImpl_stop(JNIEnv *env, jobject thiz, jint channelID) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_stop"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->stop(channelID); +} + +static void +android_media_SoundPool_SoundPoolImpl_setVolume(JNIEnv *env, jobject thiz, jint channelID, + float leftVolume, float rightVolume) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_setVolume"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setVolume(channelID, leftVolume, rightVolume); +} + +static void +android_media_SoundPool_SoundPoolImpl_setPriority(JNIEnv *env, jobject thiz, jint channelID, + int priority) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_setPriority"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setPriority(channelID, priority); +} + +static void +android_media_SoundPool_SoundPoolImpl_setLoop(JNIEnv *env, jobject thiz, jint channelID, + int loop) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_setLoop"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setLoop(channelID, loop); +} + +static void +android_media_SoundPool_SoundPoolImpl_setRate(JNIEnv *env, jobject thiz, jint channelID, + float rate) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_setRate"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setRate(channelID, rate); +} + +static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user) +{ + ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user); + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->CallStaticVoidMethod(fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2, NULL); +} + +static jint +android_media_SoundPool_SoundPoolImpl_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, jint maxChannels, jint streamType, jint srcQuality) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_native_setup"); + SoundPool *ap = new SoundPool(maxChannels, (audio_stream_type_t) streamType, srcQuality); + if (ap == NULL) { + return -1; + } + + // save pointer to SoundPool C++ object in opaque field in Java object + env->SetIntField(thiz, fields.mNativeContext, (int)ap); + + // set callback with weak reference + jobject globalWeakRef = env->NewGlobalRef(weakRef); + ap->setCallback(android_media_callback, globalWeakRef); + return 0; +} + +static void +android_media_SoundPool_SoundPoolImpl_release(JNIEnv *env, jobject thiz) +{ + ALOGV("android_media_SoundPool_SoundPoolImpl_release"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap != NULL) { + + // release weak reference + jobject weakRef = (jobject) ap->getUserData(); + if (weakRef != NULL) { + env->DeleteGlobalRef(weakRef); + } + + // clear callback and native context + ap->setCallback(NULL, NULL); + env->SetIntField(thiz, fields.mNativeContext, 0); + delete ap; + } +} + +// ---------------------------------------------------------------------------- + +// Dalvik VM type signatures +static JNINativeMethod gMethods[] = { + { "_load", + "(Ljava/lang/String;I)I", + (void *)android_media_SoundPool_SoundPoolImpl_load_URL + }, + { "_load", + "(Ljava/io/FileDescriptor;JJI)I", + (void *)android_media_SoundPool_SoundPoolImpl_load_FD + }, + { "unload", + "(I)Z", + (void *)android_media_SoundPool_SoundPoolImpl_unload + }, + { "play", + "(IFFIIF)I", + (void *)android_media_SoundPool_SoundPoolImpl_play + }, + { "pause", + "(I)V", + (void *)android_media_SoundPool_SoundPoolImpl_pause + }, + { "resume", + "(I)V", + (void *)android_media_SoundPool_SoundPoolImpl_resume + }, + { "autoPause", + "()V", + (void *)android_media_SoundPool_SoundPoolImpl_autoPause + }, + { "autoResume", + "()V", + (void *)android_media_SoundPool_SoundPoolImpl_autoResume + }, + { "stop", + "(I)V", + (void *)android_media_SoundPool_SoundPoolImpl_stop + }, + { "setVolume", + "(IFF)V", + (void *)android_media_SoundPool_SoundPoolImpl_setVolume + }, + { "setPriority", + "(II)V", + (void *)android_media_SoundPool_SoundPoolImpl_setPriority + }, + { "setLoop", + "(II)V", + (void *)android_media_SoundPool_SoundPoolImpl_setLoop + }, + { "setRate", + "(IF)V", + (void *)android_media_SoundPool_SoundPoolImpl_setRate + }, + { "native_setup", + "(Ljava/lang/Object;III)I", + (void*)android_media_SoundPool_SoundPoolImpl_native_setup + }, + { "release", + "()V", + (void*)android_media_SoundPool_SoundPoolImpl_release + } +}; + +static const char* const kClassPathName = "android/media/SoundPool$SoundPoolImpl"; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + jclass clazz; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + ALOGE("ERROR: GetEnv failed\n"); + goto bail; + } + assert(env != NULL); + + clazz = env->FindClass(kClassPathName); + if (clazz == NULL) { + ALOGE("Can't find %s", kClassPathName); + goto bail; + } + + fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "I"); + if (fields.mNativeContext == NULL) { + ALOGE("Can't find SoundPoolImpl.mNativeContext"); + goto bail; + } + + fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative", + "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + if (fields.mPostEvent == NULL) { + ALOGE("Can't find android/media/SoundPoolImpl.postEventFromNative"); + goto bail; + } + + // create a reference to class. Technically, we're leaking this reference + // since it's a static object. + fields.mSoundPoolClass = (jclass) env->NewGlobalRef(clazz); + + if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0) + goto bail; + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + +bail: + return result; +} diff --git a/media/libdrm/NOTICE b/media/libdrm/NOTICE deleted file mode 100644 index c5b1efa7aac764ae6d8da63476a2d5cec02a6a5d..0000000000000000000000000000000000000000 --- a/media/libdrm/NOTICE +++ /dev/null @@ -1,190 +0,0 @@ - - Copyright (c) 2005-2008, 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. - - 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. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - diff --git a/media/libdrm/mobile1/Android.mk b/media/libdrm/mobile1/Android.mk deleted file mode 100644 index 7356f469589b9c417cbfe6aacd2ac75446b391ea..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/Android.mk +++ /dev/null @@ -1,83 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -# --------------------------------------- -# First project -# -# Build DRM1 core library -# -# Output: libdrm1.so -# --------------------------------------- -include $(CLEAR_VARS) - -ifeq ($(TARGET_ARCH), arm) -LOCAL_DRM_CFLAG = -DDRM_DEVICE_ARCH_ARM -endif - -ifeq ($(TARGET_ARCH), x86) -LOCAL_DRM_CFLAG = -DDRM_DEVICE_ARCH_X86 -endif - -# DRM 1.0 core source files -LOCAL_SRC_FILES := \ - src/objmng/drm_decoder.c \ - src/objmng/drm_file.c \ - src/objmng/drm_i18n.c \ - src/objmng/drm_time.c \ - src/objmng/drm_api.c \ - src/objmng/drm_rights_manager.c \ - src/parser/parser_dcf.c \ - src/parser/parser_dm.c \ - src/parser/parser_rel.c \ - src/xml/xml_tinyparser.c - -# Header files path -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/include/objmng \ - $(LOCAL_PATH)/include/parser \ - $(LOCAL_PATH)/include/xml \ - external/openssl/include \ - $(call include-path-for, system-core)/cutils - -LOCAL_CFLAGS := $(LOCAL_DRM_CFLAG) - -LOCAL_SHARED_LIBRARIES := \ - libutils \ - libcutils \ - liblog \ - libcrypto - -LOCAL_MODULE := libdrm1 - -include $(BUILD_SHARED_LIBRARY) - -# --------------------------------------- -# Second project -# -# Build DRM1 Java Native Interface(JNI) library -# -# Output: libdrm1_jni.so -# ------------------------------------------------ -include $(CLEAR_VARS) - -# Source files of DRM1 Java Native Interfaces -LOCAL_SRC_FILES := \ - src/jni/drm1_jni.c - -# Header files path -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/include/parser \ - $(JNI_H_INCLUDE) \ - $(call include-path-for, system-core)/cutils - - -LOCAL_SHARED_LIBRARIES := libdrm1 \ - libnativehelper \ - libutils \ - libcutils \ - liblog - -LOCAL_MODULE := libdrm1_jni - -include $(BUILD_SHARED_LIBRARY) diff --git a/media/libdrm/mobile1/include/drm_common_types.h b/media/libdrm/mobile1/include/drm_common_types.h deleted file mode 100644 index c6bea614291f8fbac0064940fbb054e5282882d0..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/drm_common_types.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __COMMON_TYPES_H__ -#define __COMMON_TYPES_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include -#include - -#ifndef TRUE -#define TRUE 1 -#endif - -#ifndef FALSE -#define FALSE 0 -#endif - -#define Trace(...) - -#ifdef __cplusplus -} -#endif - -#endif /* __COMMON_TYPES_H__ */ diff --git a/media/libdrm/mobile1/include/jni/drm1_jni.h b/media/libdrm/mobile1/include/jni/drm1_jni.h deleted file mode 100644 index 64e78ada82cda40874bd9bc36f5f8b5501979be4..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/jni/drm1_jni.h +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __DRM1_JNI_H__ -#define __DRM1_JNI_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class android_drm_mobile1_DrmRawContent */ - -#undef android_drm_mobile1_DrmRawContent_DRM_FORWARD_LOCK -#define android_drm_mobile1_DrmRawContent_DRM_FORWARD_LOCK 1L -#undef android_drm_mobile1_DrmRawContent_DRM_COMBINED_DELIVERY -#define android_drm_mobile1_DrmRawContent_DRM_COMBINED_DELIVERY 2L -#undef android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY -#define android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY 3L -#undef android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY_DM -#define android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY_DM 4L -#undef android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_MESSAGE -#define android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_MESSAGE 1L -#undef android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_CONTENT -#define android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_CONTENT 2L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_SUCCESS -#define android_drm_mobile1_DrmRawContent_JNI_DRM_SUCCESS 0L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_FAILURE -#define android_drm_mobile1_DrmRawContent_JNI_DRM_FAILURE -1L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_EOF -#define android_drm_mobile1_DrmRawContent_JNI_DRM_EOF -2L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_UNKNOWN_DATA_LEN -#define android_drm_mobile1_DrmRawContent_JNI_DRM_UNKNOWN_DATA_LEN -3L -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeConstructDrmContent - * Signature: (Ljava/io/InputStream;II)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent - (JNIEnv *, jobject, jobject, jint, jint); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetRightsAddress - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetDeliveryMethod - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeReadPieceOfContent - * Signature: ([BIII)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeReadContent - (JNIEnv *, jobject, jbyteArray, jint, jint, jint); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetContentType - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetContentType - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetContentLength - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: finalize - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_android_drm_mobile1_DrmRawContent_finalize - (JNIEnv *, jobject); - -/* Header for class android_drm_mobile1_DrmRights */ - -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_PLAY -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_PLAY 1L -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_DISPLAY -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_DISPLAY 2L -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_EXECUTE -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_EXECUTE 3L -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_PRINT -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_PRINT 4L -#undef android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_SUCCESS -#define android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_SUCCESS 0L -#undef android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_FAILURE -#define android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_FAILURE -1L -#undef android_drm_mobile1_DrmRights_JNI_DRM_SUCCESS -#define android_drm_mobile1_DrmRights_JNI_DRM_SUCCESS 0L -#undef android_drm_mobile1_DrmRights_JNI_DRM_FAILURE -#define android_drm_mobile1_DrmRights_JNI_DRM_FAILURE -1L -/* - * Class: android_drm_mobile1_DrmRights - * Method: nativeGetConstraintInfo - * Signature: (ILandroid/drm/mobile1/DrmConstraintInfo;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo - (JNIEnv *, jobject, jint, jobject); - -/* - * Class: android_drm_mobile1_DrmRights - * Method: nativeConsumeRights - * Signature: (I)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRights_nativeConsumeRights - (JNIEnv *, jobject, jint); - -/* Header for class android_drm_mobile1_DrmRightsManager */ - -#undef android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_XML -#define android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_XML 3L -#undef android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_WBXML -#define android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_WBXML 4L -#undef android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_MESSAGE -#define android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_MESSAGE 1L -#undef android_drm_mobile1_DrmRightsManager_JNI_DRM_SUCCESS -#define android_drm_mobile1_DrmRightsManager_JNI_DRM_SUCCESS 0L -#undef android_drm_mobile1_DrmRightsManager_JNI_DRM_FAILURE -#define android_drm_mobile1_DrmRightsManager_JNI_DRM_FAILURE -1L -/* Inaccessible static: singleton */ -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeInstallDrmRights - * Signature: (Ljava/io/InputStream;IILandroid/drm/mobile1/DrmRights;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights - (JNIEnv *, jobject, jobject, jint, jint, jobject); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeQueryRights - * Signature: (Landroid/drm/mobile1/DrmRawContent;Landroid/drm/mobile1/DrmRights;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights - (JNIEnv *, jobject, jobject, jobject); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeGetRightsNumber - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeGetRightsList - * Signature: ([Landroid/drm/mobile1/DrmRights;I)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList - (JNIEnv *, jobject, jobjectArray, jint); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeDeleteRights - * Signature: (Landroid/drm/mobile1/DrmRights;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights - (JNIEnv *, jobject, jobject); - -/** - * DRM return value defines - */ -#define JNI_DRM_SUCCESS \ - android_drm_mobile1_DrmRawContent_JNI_DRM_SUCCESS /**< Successful operation */ -#define JNI_DRM_FAILURE \ - android_drm_mobile1_DrmRawContent_JNI_DRM_FAILURE /**< General failure */ -#define JNI_DRM_EOF \ - android_drm_mobile1_DrmRawContent_JNI_DRM_EOF /**< Indicates the end of the DRM content is reached */ -#define JNI_DRM_UNKNOWN_DATA_LEN \ - android_drm_mobile1_DrmRawContent_JNI_DRM_UNKNOWN_DATA_LEN /**< Indicates the data length is unknown */ - -/** - * DRM MIME type defines - */ -#define JNI_DRM_MIMETYPE_MESSAGE \ - android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_MESSAGE /**< The "application/vnd.oma.drm.message" MIME type */ -#define JNI_DRM_MIMETYPE_CONTENT \ - android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_CONTENT /**< The "application/vnd.oma.drm.content" MIME type */ -#define JNI_DRM_MIMETYPE_RIGHTS_XML \ - android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_XML /**< The "application/vnd.oma.drm.rights+xml" MIME type */ -#define JNI_DRM_MIMETYPE_RIGHTS_WBXML \ - android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_WBXML /**< The "application/vnd.oma.drm.rights+wbxml" MIME type */ - -/** - * DRM permission defines - */ -#define JNI_DRM_PERMISSION_PLAY \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_PLAY /**< The permission to play */ -#define JNI_DRM_PERMISSION_DISPLAY \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_DISPLAY /**< The permission to display */ -#define JNI_DRM_PERMISSION_EXECUTE \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_EXECUTE /**< The permission to execute */ -#define JNI_DRM_PERMISSION_PRINT \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_PRINT /**< The permission to print */ - -/** - * DRM delivery type defines - */ -#define JNI_DRM_FORWARD_LOCK \ - android_drm_mobile1_DrmRawContent_DRM_FORWARD_LOCK /**< forward lock */ -#define JNI_DRM_COMBINED_DELIVERY \ - android_drm_mobile1_DrmRawContent_DRM_COMBINED_DELIVERY /**< combined delivery */ -#define JNI_DRM_SEPARATE_DELIVERY \ - android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY /**< separate delivery */ -#define JNI_DRM_SEPARATE_DELIVERY_DM \ - android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY_DM /**< separate delivery DRM message */ -#ifdef __cplusplus -} -#endif -#endif /* __DRM1_JNI_H__ */ - diff --git a/media/libdrm/mobile1/include/objmng/drm_decoder.h b/media/libdrm/mobile1/include/objmng/drm_decoder.h deleted file mode 100644 index a769c81b11af668ac39c220256d6b35f87bbd47d..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/objmng/drm_decoder.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -/** - * @file drm_decoder.h - * - * provide service to decode base64 data. - * - * - * \section drm decoder interface - * - drm_decodeBase64() - * - */ - -#ifndef __DRM_DECODER_H__ -#define __DRM_DECODER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** - * Decode base64 - * \param dest dest buffer to save decode base64 data - * \param destLen dest buffer length - * \param src source data to be decoded - * \param srcLen source buffer length, and when return, give out how many bytes has been decoded - * \return - * -when success, return a positive integer of dest buffer length, - * if input dest buffer is NULL or destLen is 0, - * return dest buffer length that user should allocate to save decoding data - * -when failed, return -1 - */ -int32_t drm_decodeBase64(uint8_t * dest, int32_t destLen, uint8_t * src, int32_t * srcLen); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_DECODER_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_file.h b/media/libdrm/mobile1/include/objmng/drm_file.h deleted file mode 100644 index b94ddd01ce25dcba931daece603e94ffa6c142ab..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/objmng/drm_file.h +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - - -/** - * File Porting Layer. - */ -#ifndef __DRM_FILE_H__ -#define __DRM_FILE_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** Type value of a regular file or file name. */ -#define DRM_FILE_ISREG 1 -/** Type value of a directory or directory name. */ -#define DRM_FILE_ISDIR 2 -/** Type value of a filter name */ -#define DRM_FILE_ISFILTER 3 - - -/** Return code that indicates successful completion of an operation. */ -#define DRM_FILE_SUCCESS 0 -/** Indicates that an operation failed. */ -#define DRM_FILE_FAILURE -1 -/** Indicates that the a DRM_file_read() call reached the end of the file. */ -#define DRM_FILE_EOF -2 - - -/** Open for read access. */ -#define DRM_FILE_MODE_READ 1 -/** Open for write access. */ -#define DRM_FILE_MODE_WRITE 2 - - -#ifndef MAX_FILENAME_LEN -/** Maximum number of characters that a filename may have. By default assumes - * that the entry results of DRM_file_listNextEntry() are returned in the async state - * buffer, after the #DRM_file_result_s, and calculates the maximum name - * from that. - */ -#define MAX_FILENAME_LEN 1024 -#endif - - -/** - * Performs one-time initialization of the File System (FS). - * This function is called once during the lifetime of an application, - * and before any call to DRM_file_* functions by this application. - * When several applications are using the file interface, this function may be called - * several times, once per application. - * - * @return #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_startup(void); - -/** - * Returns the length of a file (by name, opened or unopened). - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number characters encoded in name. - * asynchronous operation returns #DRM_FILE_WOULDBLOCK. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_FAILURE or the file length. - */ -int32_t DRM_file_getFileLength(const uint16_t* name, - int32_t nameChars); - -/** - * Initializes a list iteration session. - * - * @param prefix Prefix that must be matched, UCS-2 encoded. * - * @param prefixChars Number characters encoded in prefix. - * @param session List session identifier. - * @param iteration List iteration identifier. - * - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_listOpen(const uint16_t* prefix, - int32_t prefixChars, - int32_t* session, - int32_t* iteration); - -/** - * Used to fetch a list of file names that match a given name prefix. - * - * @param prefix See DRM_file_listOpen(). This does not change during the - * iteration session. - * @param prefixChars See DRM_file_listOpen(). This does not change during - * the iteration session. - * @param entry Buffer parameter to return the next file name that matches the - * #prefix parameter, if any, when the function returns a positive number of - * characters. - * @param entryBytes Size of entry in bytes. - * @param session See DRM_file_listOpen(). - * @param iteration See DRM_file_listOpen(). - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_FAILURE or the number of - * characters encoded in entry. Returns 0 when the end of the list is reached. - */ -int32_t DRM_file_listNextEntry(const uint16_t* prefix, - int32_t prefixChars, - uint16_t* entry, - int32_t entryBytes, - int32_t* session, - int32_t* iteration); - -/** - * Ends a list iteration session. Notifies the implementation - * that the list session is over and that any session resources - * can be released. - * - * @param session See DRM_file_listOpen(). - * @param iteration See DRM_file_listOpen(). - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_listClose(int32_t session, int32_t iteration); - -/** - * Renames a file, given its old name. The file or directory is renamed - * immediately on the actual file system upon invocation of this method. - * Any open handles on the file specified by oldName become invalid after - * this method has been called. - * - * @param oldName Current file name (unopened), UCS-2 encoded. - * @param oldNameChars Number of characters encoded on oldName. - * @param newName New name for the file (unopened), UCS-2 encoded. - * @param newNameChars Number of characters encoded on newName. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. In particular, - * #DRM_FILE_FAILURE if a file or directory already exists with the new name. - */ -int32_t DRM_file_rename(const uint16_t* oldName, - int32_t oldNameChars, - const uint16_t* newName, - int32_t newNameChars); - -/** - * Tests if a file exists given its name. - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_ISREG, #DRM_FILE_ISDIR, #DRM_FILE_FAILURE. If name - * exists, returns #DRM_FILE_ISREG if it is a regular file and #DRM_FILE_ISDIR if it is a directory. - * Returns #DRM_FILE_FAILURE in all other cases, including those where name exists but is neither - * a regular file nor a directory. Platforms that do not support directories MUST NOT return - * #DRM_FILE_ISDIR. - */ -int32_t DRM_file_exists(const uint16_t* name, - int32_t nameChars); - -/** - * Opens a file with the given name and returns its file handle. - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @param mode Any combination of the #DRM_FILE_MODE_READ and - * #DRM_FILE_MODE_WRITE flags. If the file does not exist and mode contains the - * #DRM_FILE_MODE_WRITE flag, then the file is automatically created. If the - * file exists and the mode contains the #DRM_FILE_MODE_WRITE flag, the file is - * opened so it can be modified, but the data is not modified by the open call. - * In all cases the current position is set to the start of the file. - * The following table shows how to map the mode semantics above to UNIX - * fopen-style modes. For brevity in the table, R=#DRM_FILE_MODE_READ, - * W=#DRM_FILE_MODE_WRITE, E=File exists: - * - * - * - * - * - * - * - * - * - * - *
        RWEMaps-to
        000Return #DRM_FILE_FAILURE
        001Return #DRM_FILE_FAILURE
        010Use fopen mode "w"
        011Use fopen mode "a" and fseek to the start
        100Return #DRM_FILE_FAILURE
        101Use fopen mode "r"
        110Use fopen mode "w+"
        111Use fopen mode "r+"
        - * @param handle Pointer where the result handle value is placed when the function - * is called synchronously. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_open(const uint16_t* name, - int32_t nameChars, - int32_t mode, - int32_t* handle); - -/** - * Deletes a file given its name, UCS-2 encoded. The file or directory is - * deleted immediately on the actual file system upon invocation of this - * method. Any open handles on the file specified by name become invalid - * after this method has been called. - * - * If the port needs to ensure that a specific application does not exceed a given storage - * space quota, then the bytes freed by the deletion must be added to the available space for - * that application. - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_delete(const uint16_t* name, - int32_t nameChars); - -/** - * Read bytes from a file at the current position to a buffer. Afterwards the - * new file position is the byte after the last byte read. - * DRM_FILE_FAILURE is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete, DRM_file_rename, or DRM_file_close). - * - * @param handle File handle as returned by DRM_file_open(). - * @param dst Buffer where the data is to be copied. - * @param length Number of bytes to be copied. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE, #DRM_FILE_EOF - * or the number of bytes that were read, i.e. in the range 0..length. - */ -int32_t DRM_file_read(int32_t handle, - uint8_t* dst, - int32_t length); - -/** - * Write bytes from a buffer to the file at the current position. If the - * current position + number of bytes written > current size of the file, - * then the file is grown. Afterwards the new file position is the byte - * after the last byte written. - * DRM_FILE_FAILURE is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete, DRM_file_rename, or DRM_file_close). - * - * @param handle File handle as returned by DRM_file_open(). - * @param src Buffer that contains the bytes to be written. - * @param length Number of bytes to be written. - * If the port needs to ensure that a specific application does not exceed a given storage - * space quota, the implementation must make sure the call does not violate that invariant. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_FAILURE or the number of bytes - * that were written. This number must be in the range 0..length. - * Returns #DRM_FILE_FAILURE when storage is full or exceeds quota. - */ -int32_t DRM_file_write(int32_t handle, - const uint8_t* src, - int32_t length); - -/** - * Closes a file. - * DRM_FILE_SUCCESS is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete or DRM_file_rename). - * - * @param handle File handle as returned by DRM_file_open(). - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_close(int32_t handle); - -/** - * Sets the current position in an opened file. - * DRM_FILE_FAILURE is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete, DRM_file_rename, or DRM_file_close). - * - * @param handle File handle as returned by DRM_file_open(). - * @param value The new current position of the file. If value is greater - * than the length of the file then the file should be extended. The contents - * of the newly extended portion of the file is undefined. - * If the port needs to ensure that a specific application does not exceed a given storage - * space quota, the implementation must make sure the call does not violate that invariant. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - * Returns #DRM_FILE_FAILURE when storage is full or exceeds quota. - */ -int32_t DRM_file_setPosition(int32_t handle, int32_t value); - -/** - * Creates a directory with the assigned name and full file permissions on - * the file system. The full path to the new directory must already exist. - * The directory is created immediately on the actual file system upon - * invocation of this method. - * - * @param name Name of the directory, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_mkdir(const uint16_t* name, - int32_t nameChars); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_FILE_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_i18n.h b/media/libdrm/mobile1/include/objmng/drm_i18n.h deleted file mode 100644 index 7487e9b084f26ac4d4e644d050a5a0385561dbfa..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/objmng/drm_i18n.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - - -#ifndef __DRM_I18N_H__ -#define __DRM_I18N_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** - * @name Charset value defines - * @ingroup i18n - * - * Charset value defines - * see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_81rn.asp - */ -typedef enum { - DRM_CHARSET_GBK = 936, /** Simplified Chinese GBK (CP936) */ - DRM_CHARSET_GB2312 = 20936, /** Simplified Chinese GB2312 (CP936) */ - DRM_CHARSET_BIG5 = 950, /** BIG5 (CP950) */ - DRM_CHARSET_LATIN1 = 28591, /** ISO 8859-1, Latin 1 */ - DRM_CHARSET_LATIN2 = 28592, /** ISO 8859-2, Latin 2 */ - DRM_CHARSET_LATIN3 = 28593, /** ISO 8859-3, Latin 3 */ - DRM_CHARSET_LATIN4 = 28594, /** ISO 8859-4, Latin 4 */ - DRM_CHARSET_CYRILLIC = 28595, /** ISO 8859-5, Cyrillic */ - DRM_CHARSET_ARABIC = 28596, /** ISO 8859-6, Arabic */ - DRM_CHARSET_GREEK = 28597, /** ISO 8859-7, Greek */ - DRM_CHARSET_HEBREW = 28598, /** ISO 8859-8, Hebrew */ - DRM_CHARSET_LATIN5 = 28599, /** ISO 8859-9, Latin 5 */ - DRM_CHARSET_LATIN6 = 865, /** ISO 8859-10, Latin 6 (not sure here) */ - DRM_CHARSET_THAI = 874, /** ISO 8859-11, Thai */ - DRM_CHARSET_LATIN7 = 1257, /** ISO 8859-13, Latin 7 (not sure here) */ - DRM_CHARSET_LATIN8 = 38598, /** ISO 8859-14, Latin 8 (not sure here) */ - DRM_CHARSET_LATIN9 = 28605, /** ISO 8859-15, Latin 9 */ - DRM_CHARSET_LATIN10 = 28606, /** ISO 8859-16, Latin 10 */ - DRM_CHARSET_UTF8 = 65001, /** UTF-8 */ - DRM_CHARSET_UTF16LE = 1200, /** UTF-16 LE */ - DRM_CHARSET_UTF16BE = 1201, /** UTF-16 BE */ - DRM_CHARSET_HINDI = 57002, /** Hindi/Mac Devanagari */ - DRM_CHARSET_UNSUPPORTED = -1 -} DRM_Charset_t; - -/** - * Convert multibyte string of specified charset to unicode string. - * Note NO terminating '\0' will be appended to the output unicode string. - * - * @param charset Charset of the multibyte string. - * @param mbs Multibyte string to be converted. - * @param mbsLen Number of the bytes (in mbs) to be converted. - * @param wcsBuf Buffer for the converted unicode characters. - * If wcsBuf is NULL, the function returns the number of unicode - * characters required for the buffer. - * @param bufSizeInWideChar The size (in wide char) of wcsBuf - * @param bytesConsumed The number of bytes in mbs that have been successfully - * converted. The value of *bytesConsumed is undefined - * if wcsBuf is NULL. - * - * @return Number of the successfully converted unicode characters if wcsBuf - * is not NULL. If wcsBuf is NULL, returns required unicode buffer - * size. -1 for unrecoverable errors. - */ -int32_t DRM_i18n_mbsToWcs(DRM_Charset_t charset, - const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert unicode string to multibyte string with specified charset. - * Note NO terminating '\0' will be appended to the output multibyte string. - * - * @param charset Charset of the multibyte string to be converted to. - * @param wcs Unicode string to be converted. - * @param wcsLen Number of the unicode characters (in wcs) to be converted. - * @param mbsBuf Buffer for converted multibyte characters. - * If mbsBuf is NULL, the function returns the number of bytes - * required for the buffer. - * @param bufSizeInByte The size (in byte) of mbsBuf. - * - * @return Number of the successfully converted bytes. - */ -int32_t DRM_i18n_wcsToMbs(DRM_Charset_t charset, - const uint16_t *wcs, int32_t wcsLen, - uint8_t *mbsBuf, int32_t bufSizeInByte); - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/media/libdrm/mobile1/include/objmng/drm_inner.h b/media/libdrm/mobile1/include/objmng/drm_inner.h deleted file mode 100644 index 55234f82cd3b775d4825f5d22a19387e3126efdf..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/objmng/drm_inner.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __DRM_INNER_H__ -#define __DRM_INNER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define INT_2_YMD_HMS(year, mon, day, date, hour, min, sec, time) do{\ - year = date / 10000;\ - mon = date % 10000 / 100;\ - day = date %100;\ - hour = time / 10000;\ - min = time % 10000 / 100;\ - sec = time % 100;\ -}while(0) - -/** - * Define the max malloc length for a DRM. - */ -#define DRM_MAX_MALLOC_LEN (50 * 1024) /* 50K */ - -#define DRM_ONE_AES_BLOCK_LEN 16 -#define DRM_TWO_AES_BLOCK_LEN 32 - -typedef struct _T_DRM_DM_Binary_Node { - uint8_t boundary[256]; -} T_DRM_DM_Binary_Node; - -typedef struct _T_DRM_DM_Base64_Node { - uint8_t boundary[256]; - uint8_t b64DecodeData[4]; - int32_t b64DecodeDataLen; -} T_DRM_DM_Base64_Node; - -typedef struct _T_DRM_Dcf_Node { - uint8_t rightsIssuer[256]; - int32_t encContentLength; - uint8_t aesDecData[16]; - int32_t aesDecDataLen; - int32_t aesDecDataOff; - uint8_t aesBackupBuf[16]; - int32_t bAesBackupBuf; -} T_DRM_Dcf_Node; - -typedef struct _T_DRM_Session_Node { - int32_t sessionId; - int32_t inputHandle; - int32_t mimeType; - int32_t (*getInputDataLengthFunc)(int32_t inputHandle); - int32_t (*readInputDataFunc)(int32_t inputHandle, uint8_t* buf, int32_t bufLen); - int32_t (*seekInputDataFunc)(int32_t inputHandle, int32_t offset); - int32_t deliveryMethod; - int32_t transferEncoding; - uint8_t contentType[64]; - int32_t contentLength; - int32_t contentOffset; - uint8_t contentID[256]; - uint8_t* rawContent; - int32_t rawContentLen; - int32_t bEndData; - uint8_t* readBuf; - int32_t readBufLen; - int32_t readBufOff; - void* infoStruct; - struct _T_DRM_Session_Node* next; -} T_DRM_Session_Node; - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_INNER_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_rights_manager.h b/media/libdrm/mobile1/include/objmng/drm_rights_manager.h deleted file mode 100644 index d81e7a18a57f38caf1d7541814b043efb03db014..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/objmng/drm_rights_manager.h +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __DRM_RIGHTS_MANAGER_H__ -#define __DRM_RIGHTS_MANAGER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#ifdef DRM_DEVICE_ARCH_ARM -#define ANDROID_DRM_CORE_PATH "/data/drm/rights/" -#define DRM_UID_FILE_PATH "/data/drm/rights/uid.txt" -#else -#define ANDROID_DRM_CORE_PATH "/home/user/golf/esmertec/device/out/debug/host/linux-x86/product/sim/data/data/com.android.drm.mobile1/" -#define DRM_UID_FILE_PATH "/home/user/golf/esmertec/device/out/debug/host/linux-x86/product/sim/data/data/com.android.drm.mobile1/uid.txt" -#endif - -#define EXTENSION_NAME_INFO ".info" - -#define GET_ID 1 -#define GET_UID 2 - -#define GET_ROAMOUNT 1 -#define GET_ALL_RO 2 -#define SAVE_ALL_RO 3 -#define GET_A_RO 4 -#define SAVE_A_RO 5 - -/** - * Get the id or uid from the "uid.txt" file. - * - * \param Uid The content id for a specially DRM object. - * \param id The id number managed by DRM engine for a specially DRM object. - * \param option The option to get id or uid, the value includes: GET_ID, GET_UID. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_readFromUidTxt(uint8_t* Uid, int32_t* id, int32_t option); - -/** - * Save or read the rights information on the "id.info" file. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * \param Ro The rights structure to save the rights information. - * \param RoAmount The number of rights for this DRM object. - * \param option The option include: GET_ROAMOUNT, GET_ALL_RO, SAVE_ALL_RO, GET_A_RO, SAVE_A_RO. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_writeOrReadInfo(int32_t id, T_DRM_Rights* Ro, int32_t* RoAmount, int32_t option); - -/** - * Append a rights information to DRM engine storage. - * - * \param Ro The rights structure to save the rights information. - * - * return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_appendRightsInfo(T_DRM_Rights* rights); - -/** - * Get the mex id number from the "uid.txt" file. - * - * \return - * -an integer to indicate the max id number. - * -(-1), if the operation failed. - */ -int32_t drm_getMaxIdFromUidTxt(); - -/** - * Remove the "id.info" file if all the rights for this DRM object has been deleted. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_removeIdInfoFile(int32_t id); - -/** - * Update the "uid.txt" file when delete the rights object. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_updateUidTxtWhenDelete(int32_t id); - -/** - * Get the CEK according the given content id. - * - * \param uid The content id for a specially DRM object. - * \param KeyValue The buffer to save the CEK. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_getKey(uint8_t* uid, uint8_t* KeyValue); - -/** - * Discard the padding bytes in DCF decrypted data. - * - * \param decryptedBuf The aes decrypted data buffer to be scanned. - * \param decryptedBufLen The length of the buffer. And save the output result. - * - * \return - * -0 - */ -void drm_discardPaddingByte(uint8_t *decryptedBuf, int32_t *decryptedBufLen); - -/** - * Decrypt the media data according the CEK. - * - * \param Buffer The buffer to decrypted and also used to save the output data. - * \param BufferLen The length of the buffer data and also save the output data length. - * \param key The structure of the CEK. - * - * \return - * -0 - */ -int32_t drm_aesDecBuffer(uint8_t * Buffer, int32_t * BufferLen, AES_KEY *key); - -/** - * Update the DCF data length according the CEK. - * - * \param pDcfLastData The last several byte for the DCF. - * \param keyValue The CEK of the DRM content. - * \param moreBytes Output the more bytes for discarded. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_updateDcfDataLen(uint8_t* pDcfLastData, uint8_t* keyValue, int32_t* moreBytes); - -/** - * Check and update the rights for a specially DRM content. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * \param permission The permission to be check and updated. - * - * \return - * -DRM_SUCCESS, if there is a valid rights and update it successfully. - * -DRM_NO_RIGHTS, if there is no rights for this content. - * -DRM_RIGHTS_PENDING, if the rights is pending. - * -DRM_RIGHTS_EXPIRED, if the rights has expired. - * -DRM_RIGHTS_FAILURE, if there is some other error occur. - */ -int32_t drm_checkRoAndUpdate(int32_t id, int32_t permission); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_RIGHTS_MANAGER_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_time.h b/media/libdrm/mobile1/include/objmng/drm_time.h deleted file mode 100644 index 9b013e6a17bb7d1d9ad4ea267935c0e3513aef38..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/objmng/drm_time.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - - -/** - * @file - * Time Porting Layer - * - * Basic support functions that are needed by time. - * - * - * \section drm_time Interface - * - DRM_time_getElapsedSecondsFrom1970() - * - DRM_time_sleep() - * - DRM_time_getSysTime() - * - */ - -#ifndef __DRM_TIME_H__ -#define __DRM_TIME_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -/** the time format */ -typedef struct __db_system_time_ -{ - uint16_t year; - uint16_t month; - uint16_t day; - uint16_t hour; - uint16_t min; - uint16_t sec; -} T_DB_TIME_SysTime; - -/** - * Get the system time.it's up to UTC - * \return Return the time in elapsed seconds. - */ -uint32_t DRM_time_getElapsedSecondsFrom1970(void); - -/** - * Suspend the execution of the current thread for a specified interval - * \param ms suspended time by millisecond - */ -void DRM_time_sleep(uint32_t ms); - -/** - * function: get current system time - * \param time_ptr[OUT] the system time got - * \attention - * time_ptr must not be NULL - */ -void DRM_time_getSysTime(T_DB_TIME_SysTime *time_ptr); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_TIME_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/svc_drm.h b/media/libdrm/mobile1/include/objmng/svc_drm.h deleted file mode 100644 index 789343fc786585ab4cf6078b456622c497a46d06..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/objmng/svc_drm.h +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __SVC_DRM_NEW_H__ -#define __SVC_DRM_NEW_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** - * Define the mime type of DRM data. - */ -#define TYPE_DRM_MESSAGE 0x48 /**< The mime type is "application/vnd.oma.drm.message" */ -#define TYPE_DRM_CONTENT 0x49 /**< The mime type is "application/vnd.oma.drm.content" */ -#define TYPE_DRM_RIGHTS_XML 0x4a /**< The mime type is "application/vnd.oma.drm.rights+xml" */ -#define TYPE_DRM_RIGHTS_WBXML 0x4b /**< The mime type is "application/vnd.oma.drm.rights+wbxml" */ -#define TYPE_DRM_UNKNOWN 0xff /**< The mime type is unknown */ - -/** - * Define the delivery methods. - */ -#define FORWARD_LOCK 1 /**< Forward_lock */ -#define COMBINED_DELIVERY 2 /**< Combined delivery */ -#define SEPARATE_DELIVERY 3 /**< Separate delivery */ -#define SEPARATE_DELIVERY_FL 4 /**< Separate delivery but DCF is forward-lock */ - -/** - * Define the permissions. - */ -#define DRM_PERMISSION_PLAY 0x01 /**< Play */ -#define DRM_PERMISSION_DISPLAY 0x02 /**< Display */ -#define DRM_PERMISSION_EXECUTE 0x04 /**< Execute */ -#define DRM_PERMISSION_PRINT 0x08 /**< Print */ -#define DRM_PERMISSION_FORWARD 0x10 /**< Forward */ - -/** - * Define the constraints. - */ -#define DRM_NO_CONSTRAINT 0x80 /**< Indicate have no constraint, it can use freely */ -#define DRM_END_TIME_CONSTRAINT 0x08 /**< Indicate have end time constraint */ -#define DRM_INTERVAL_CONSTRAINT 0x04 /**< Indicate have interval constraint */ -#define DRM_COUNT_CONSTRAINT 0x02 /**< Indicate have count constraint */ -#define DRM_START_TIME_CONSTRAINT 0x01 /**< Indicate have start time constraint */ -#define DRM_NO_PERMISSION 0x00 /**< Indicate no rights */ - -/** - * Define the return values for those interface. - */ -#define DRM_SUCCESS 0 -#define DRM_FAILURE -1 -#define DRM_MEDIA_EOF -2 -#define DRM_RIGHTS_DATA_INVALID -3 -#define DRM_MEDIA_DATA_INVALID -4 -#define DRM_SESSION_NOT_OPENED -5 -#define DRM_NO_RIGHTS -6 -#define DRM_NOT_SD_METHOD -7 -#define DRM_RIGHTS_PENDING -8 -#define DRM_RIGHTS_EXPIRED -9 -#define DRM_UNKNOWN_DATA_LEN -10 - -/** - * The input DRM data structure, include DM, DCF, DR, DRC. - */ -typedef struct _T_DRM_Input_Data { - /** - * The handle of the input DRM data. - */ - int32_t inputHandle; - - /** - * The mime type of the DRM data, if the mime type set to unknown, DRM engine - * will try to scan the input data to confirm the mime type, but we must say that - * the scan and check of mime type is not strictly precise. - */ - int32_t mimeType; - - /** - * The function to get input data length, this function should be implement by out module, - * and DRM engine will call-back it. - * - * \param inputHandle The handle of the DRM data. - * - * \return - * -A positive integer indicate the length of input data. - * -0, if some error occurred. - */ - int32_t (*getInputDataLength)(int32_t inputHandle); - - /** - * The function to read the input data, this function should be implement by out module, - * and DRM engine will call-back it. - * - * \param inputHandle The handle of the DRM data. - * \param buf The buffer mallocced by DRM engine to save the data. - * \param bufLen The length of the buffer. - * - * \return - * -A positive integer indicate the actually length of byte has been read. - * -0, if some error occurred. - * -(-1), if reach to the end of the data. - */ - int32_t (*readInputData)(int32_t inputHandle, uint8_t* buf, int32_t bufLen); - - /** - * The function to seek the current file pointer, this function should be implement by out module, - * and DRM engine will call-back it. - * - * \param inputHandle The handle of the DRM data. - * \param offset The offset from the start position to be seek. - * - * \return - * -0, if seek operation success. - * -(-1), if seek operation fail. - */ - int32_t (*seekInputData)(int32_t inputHandle, int32_t offset); -} T_DRM_Input_Data; - -/** - * The constraint structure. - */ -typedef struct _T_DRM_Constraint_Info { - uint8_t indicator; /**< Whether there is a right */ - uint8_t unUsed[3]; - int32_t count; /**< The constraint of count */ - int32_t startDate; /**< The constraint of start date */ - int32_t startTime; /**< The constraint of start time */ - int32_t endDate; /**< The constraint of end date */ - int32_t endTime; /**< The constraint of end time */ - int32_t intervalDate; /**< The constraint of interval date */ - int32_t intervalTime; /**< The constraint of interval time */ -} T_DRM_Constraint_Info; - -/** - * The rights permission and constraint information structure. - */ -typedef struct _T_DRM_Rights_Info { - uint8_t roId[256]; /**< The unique id for a specially rights object */ - T_DRM_Constraint_Info playRights; /**< Constraint of play */ - T_DRM_Constraint_Info displayRights; /**< Constraint of display */ - T_DRM_Constraint_Info executeRights; /**< Constraint of execute */ - T_DRM_Constraint_Info printRights; /**< Constraint of print */ -} T_DRM_Rights_Info; - -/** - * The list node of the Rights information structure. - */ -typedef struct _T_DRM_Rights_Info_Node { - T_DRM_Rights_Info roInfo; - struct _T_DRM_Rights_Info_Node *next; -} T_DRM_Rights_Info_Node; - -/** - * Install a rights object to DRM engine, include the rights in Combined Delivery cases. - * Because all the rights object is managed by DRM engine, so every incoming rights object - * must be install to the engine first, or the DRM engine will not recognize it. - * - * \param data The rights object data or Combined Delivery case data. - * \param pRightsInfo The structure to save this rights information. - * - * \return - * -DRM_SUCCESS, when install successfully. - * -DRM_RIGHTS_DATA_INVALID, when the input rights data is invalid. - * -DRM_FAILURE, when some other error occur. - */ -int32_t SVC_drm_installRights(T_DRM_Input_Data data, T_DRM_Rights_Info* pRightsInfo); - -/** - * Open a session for a special DRM object, it will parse the input DRM data, and then user - * can try to get information for this DRM object, or try to use it if the rights is valid. - * - * \param data The DRM object data, DM or DCF. - * - * \return - * -A handle for this opened DRM object session. - * -DRM_MEDIA_DATA_INVALID, when the input DRM object data is invalid. - * -DRM_FAILURE, when some other error occurred. - */ -int32_t SVC_drm_openSession(T_DRM_Input_Data data); - -/** - * Get the delivery method of the DRM object. - * - * \param session The handle for this DRM object session. - * - * \return - * -The delivery method of this DRM object, include: FORWARD_LOCK, COMBINED_DELIVERY, SEPARATE_DELIVERY, SEPARATE_DELIVERY_FL. - * -DRM_FAILURE, when some other error occurred. - */ -int32_t SVC_drm_getDeliveryMethod(int32_t session); - -/** - * Get DRM object media object content type. - * - * \param session The handle for this DRM object session. - * \param mediaType The buffer to save the media type string, 64 bytes is enough. - * - * \return - * -DRM_SUCCESS, when get the media object content type successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getContentType(int32_t session, uint8_t* mediaType); - -/** - * Check whether a specific DRM object has the specific permission rights or not. - * - * \param session The handle for this DRM object session. - * \param permission Specify the permission to be checked. - * - * \return - * -DRM_SUCCESS, when it has the rights for the permission. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when it has no rights. - * -DRM_RIGHTS_PENDING, when it has the rights, but currently it is pending. - * -DRM_RIGHTS_EXPIRED, when the rights has expired. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_checkRights(int32_t session, int32_t permission); - -/** - * Consume the rights when try to use the DRM object. - * - * \param session The handle for this DRM object session. - * \param permission Specify the permission to be checked. - * - * \return - * -DRM_SUCCESS, when consume rights successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when it has no rights. - * -DRM_RIGHTS_PENDING, when it has the rights, but currently it is pending. - * -DRM_RIGHTS_EXPIRED, when the rights has expired. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_consumeRights(int32_t session, int32_t permission); - -/** - * Get DRM media object content data length. - * - * \param session The handle for this DRM object session. - * - * \return - * -A positive integer indicate the length of the media object content data. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when the rights object is not existed. - * -DRM_UNKNOWN_DATA_LEN, when DRM object media data length is unknown in case of DCF has no rights. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getContentLength(int32_t session); - -/** - * Get DRM media object content data. Support get the data piece by piece if the content is too large. - * - * \param session The handle for this DRM object session. - * \param offset The offset to start to get content. - * \param mediaBuf The buffer to save media object data. - * \param mediaBufLen The length of the buffer. - * - * \return - * -A positive integer indicate the actually length of the data has been got. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when the rights object is not existed. - * -DRM_MEDIA_EOF, when reach to the end of the media data. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getContent(int32_t session, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen); - -/** - * Get the rights issuer address, this interface is specially for Separate Delivery method. - * - * \param session The handle for this DRM object session. - * \param rightsIssuer The buffer to save rights issuer, 256 bytes are enough. - * - * \return - * -DRM_SUCCESS, when get the rights issuer successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NOT_SD_METHOD, when it is not a Separate Delivery DRM object. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getRightsIssuer(int32_t session, uint8_t* rightsIssuer); - -/** - * Get DRM object constraint informations. - * - * \param session The handle for this DRM object session. - * \param rights The structue to save the rights object information. - * - * \return - * -DRM_SUCCESS, when get the rights information successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when this DRM object has not rights. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getRightsInfo(int32_t session, T_DRM_Rights_Info* rights); - -/** - * Close the opened session, after closed, the handle become invalid. - * - * \param session The handle for this DRM object session. - * - * \return - * -DRM_SUCCESS, when close operation success. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_closeSession(int32_t session); - -/** - * Check and update the given rights according the given permission. - * - * \param contentID The unique id of the rights object. - * \param permission The permission to be updated. - * - * \return - * -DRM_SUCCESS, when update operation success. - * -DRM_NO_RIGHTS, when it has no rights. - * -DRM_RIGHTS_PENDING, when it has the rights, but currently it is pending. - * -DRM_RIGHTS_EXPIRED, when the rights has expired. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_updateRights(uint8_t* contentID, int32_t permission); - -/** - * Scan all the rights object in current DRM engine, and get all their information. - * - * \param ppRightsInfo The pointer to the list structure to save rights info. - * - * \return - * -DRM_SUCCESS, when get information successfully. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_viewAllRights(T_DRM_Rights_Info_Node **ppRightsInfo); - -/** - * Free the allocated memory when call "SVC_drm_viewAllRights". - * - * \param pRightsHeader The header pointer of the list to be free. - * - * \return - * -DRM_SUCCESS, when free operation successfully. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_freeRightsInfoList(T_DRM_Rights_Info_Node *pRightsHeader); - -/** - * Delete a specify rights. - * - * \param roId The unique id of the rights. - * - * \return - * -DRM_SUCCESS, when free operation successfully. - * -DRM_NO_RIGHTS, when there is not this rights object. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_deleteRights(uint8_t* roId); - -#ifdef __cplusplus -} -#endif - -#endif /* __SVC_DRM_NEW_H__ */ diff --git a/media/libdrm/mobile1/include/parser/parser_dcf.h b/media/libdrm/mobile1/include/parser/parser_dcf.h deleted file mode 100644 index c63a1951ffb17808e39e894975fe0b47ce8dacbe..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/parser/parser_dcf.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __PARSER_DCF_H__ -#define __PARSER_DCF_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define MAX_ENCRYPTION_METHOD_LEN 64 -#define MAX_RIGHTS_ISSUER_LEN 256 -#define MAX_CONTENT_NAME_LEN 64 -#define MAX_CONTENT_DESCRIPTION_LEN 256 -#define MAX_CONTENT_VENDOR_LEN 256 -#define MAX_ICON_URI_LEN 256 -#define MAX_CONTENT_TYPE_LEN 64 -#define MAX_CONTENT_URI_LEN 256 - -#define HEADER_ENCRYPTION_METHOD "Encryption-Method: " -#define HEADER_RIGHTS_ISSUER "Rights-Issuer: " -#define HEADER_CONTENT_NAME "Content-Name: " -#define HEADER_CONTENT_DESCRIPTION "Content-Description: " -#define HEADER_CONTENT_VENDOR "Content-Vendor: " -#define HEADER_ICON_URI "Icon-Uri: " - -#define HEADER_ENCRYPTION_METHOD_LEN 19 -#define HEADER_RIGHTS_ISSUER_LEN 15 -#define HEADER_CONTENT_NAME_LEN 14 -#define HEADER_CONTENT_DESCRIPTION_LEN 21 -#define HEADER_CONTENT_VENDOR_LEN 16 -#define HEADER_ICON_URI_LEN 10 - -#define UINT_VAR_FLAG 0x80 -#define UINT_VAR_DATA 0x7F -#define MAX_UINT_VAR_BYTE 5 -#define DRM_UINT_VAR_ERR -1 - -typedef struct _T_DRM_DCF_Info { - uint8_t Version; - uint8_t ContentTypeLen; /**< Length of the ContentType field */ - uint8_t ContentURILen; /**< Length of the ContentURI field */ - uint8_t unUsed; - uint8_t ContentType[MAX_CONTENT_TYPE_LEN]; /**< The MIME media type of the plaintext data */ - uint8_t ContentURI[MAX_CONTENT_URI_LEN]; /**< The unique identifier of this content object */ - int32_t HeadersLen; /**< Length of the Headers field */ - int32_t EncryptedDataLen; /**< Length of the encrypted data field */ - int32_t DecryptedDataLen; /**< Length of the decrypted data field */ - uint8_t Encryption_Method[MAX_ENCRYPTION_METHOD_LEN]; /**< Encryption method */ - uint8_t Rights_Issuer[MAX_RIGHTS_ISSUER_LEN]; /**< Rights issuer */ - uint8_t Content_Name[MAX_CONTENT_NAME_LEN]; /**< Content name */ - uint8_t ContentDescription[MAX_CONTENT_DESCRIPTION_LEN]; /**< Content description */ - uint8_t ContentVendor[MAX_CONTENT_VENDOR_LEN]; /**< Content vendor */ - uint8_t Icon_URI[MAX_ICON_URI_LEN]; /**< Icon URI */ -} T_DRM_DCF_Info; - -/** - * Parse the DRM content format data - * - * \param buffer (in)Input the DCF format data - * \param bufferLen (in)The input buffer length - * \param pDcfInfo (out)A structure pointer which contain information of DCF headers - * \param ppEncryptedData (out)The location of encrypted data - * - * \return - * -TRUE, when success - * -FALSE, when failed - */ -int32_t drm_dcfParser(uint8_t *buffer, int32_t bufferLen, T_DRM_DCF_Info *pDcfInfo, - uint8_t **ppEncryptedData); - -#ifdef __cplusplus -} -#endif - -#endif /* __PARSER_DCF_H__ */ diff --git a/media/libdrm/mobile1/include/parser/parser_dm.h b/media/libdrm/mobile1/include/parser/parser_dm.h deleted file mode 100644 index ec8b6b2c07b60f7d4877e17c14226018cc8fe3e1..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/parser/parser_dm.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __PARSER_DM_H__ -#define __PARSER_DM_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define MAX_CONTENT_TYPE_LEN 64 -#define MAX_CONTENT_ID 256 -#define MAX_CONTENT_BOUNDARY_LEN 256 -#define MAX_RIGHTS_ISSUER_LEN 256 - -#define DRM_MIME_TYPE_RIGHTS_XML "application/vnd.oma.drm.rights+xml" -#define DRM_MIME_TYPE_CONTENT "application/vnd.oma.drm.content" - -#define HEADERS_TRANSFER_CODING "Content-Transfer-Encoding:" -#define HEADERS_CONTENT_TYPE "Content-Type:" -#define HEADERS_CONTENT_ID "Content-ID:" - -#define TRANSFER_CODING_TYPE_7BIT "7bit" -#define TRANSFER_CODING_TYPE_8BIT "8bit" -#define TRANSFER_CODING_TYPE_BINARY "binary" -#define TRANSFER_CODING_TYPE_BASE64 "base64" - -#define DRM_UID_TYPE_FORWORD_LOCK "forwardlock" -#define DRM_NEW_LINE_CRLF "\r\n" - -#define HEADERS_TRANSFER_CODING_LEN 26 -#define HEADERS_CONTENT_TYPE_LEN 13 -#define HEADERS_CONTENT_ID_LEN 11 - -#define DRM_MESSAGE_CODING_7BIT 0 /* default */ -#define DRM_MESSAGE_CODING_8BIT 1 -#define DRM_MESSAGE_CODING_BINARY 2 -#define DRM_MESSAGE_CODING_BASE64 3 - -#define DRM_B64_DEC_BLOCK 3 -#define DRM_B64_ENC_BLOCK 4 - -typedef struct _T_DRM_DM_Info { - uint8_t contentType[MAX_CONTENT_TYPE_LEN]; /**< Content type */ - uint8_t contentID[MAX_CONTENT_ID]; /**< Content ID */ - uint8_t boundary[MAX_CONTENT_BOUNDARY_LEN]; /**< DRM message's boundary */ - uint8_t deliveryType; /**< The Delivery type */ - uint8_t transferEncoding; /**< Transfer encoding type */ - int32_t contentOffset; /**< The offset of the media content from the original DRM data */ - int32_t contentLen; /**< The length of the media content */ - int32_t rightsOffset; /**< The offset of the rights object in case of combined delivery */ - int32_t rightsLen; /**< The length of the rights object in case of combined delivery */ - uint8_t rightsIssuer[MAX_RIGHTS_ISSUER_LEN];/**< The rights issuer address in case of separate delivery */ -} T_DRM_DM_Info; - -/** - * Search the string in a limited length. - * - * \param str The original string - * \param strSearch The sub-string to be searched - * \param len The length limited - * - * \return - * -NULL, when there is not the searched string in length - * -The pointer of this sub-string - */ -const uint8_t* drm_strnstr(const uint8_t* str, const uint8_t* strSearch, int32_t len); - -/** - * Parse the DRM message format data. - * - * \param buffer (in)Input the DRM message format data - * \param bufferLen (in)The input buffer length - * \param pDmInfo (out)A structure pointer which contain information of DRM message headers - * - * \return - * -TRUE, when success - * -FALSE, when failed - */ -int32_t drm_parseDM(const uint8_t* buffer, int32_t bufferLen, T_DRM_DM_Info* pDmInfo); - -#ifdef __cplusplus -} -#endif - -#endif /* __PARSER_DM_H__ */ diff --git a/media/libdrm/mobile1/include/parser/parser_rel.h b/media/libdrm/mobile1/include/parser/parser_rel.h deleted file mode 100644 index 8def1997351216adb4b8e21f6b55873d46383e21..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/parser/parser_rel.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __PARSER_REL_H__ -#define __PARSER_REL_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define WRITE_RO_FLAG(whoIsAble, boolValue, Indicator, RIGHTS) do{\ - whoIsAble = boolValue;\ - Indicator |= RIGHTS;\ -}while(0) - -#define CHECK_VALIDITY(ret) do{\ - if(ret == NULL){\ - if(XML_ERROR_NO_SUCH_NODE != xml_errno)\ - return FALSE;\ - }\ - else\ - {\ - if(XML_ERROR_OK != xml_errno)\ - return FALSE;\ - }\ -}while(0) - -#define YMD_HMS_2_INT(year, mon, day, date, hour, min, sec, time) do{\ - date = year * 10000 + mon * 100 + day;\ - time = hour * 10000 + min * 100 + sec;\ -}while(0) - -#define DRM_UID_LEN 256 -#define DRM_KEY_LEN 16 - -#define XML_DOM_PARSER - -typedef struct _T_DRM_DATETIME { - int32_t date; /**< year * 10000 + mon *100 + day */ - int32_t time; /**< hour * 10000 + min *100 + sec */ -} T_DRM_DATETIME; - -typedef struct _T_DRM_Rights_Constraint { - uint8_t Indicator; /**< Indicate which is constrainted, the first one indicate 0001, second one indicate 0010 */ - uint8_t unUsed[3]; - int32_t Count; /**< The times that can be used */ - T_DRM_DATETIME StartTime; /**< The starting time */ - T_DRM_DATETIME EndTime; /**< The ending time */ - T_DRM_DATETIME Interval; /**< The interval time */ -} T_DRM_Rights_Constraint; - -typedef struct _T_DRM_Rights { - uint8_t Version[8]; /**< Version number */ - uint8_t uid[256]; /**< record the rights object name */ - uint8_t KeyValue[16]; /**< Decode base64 */ - int32_t bIsPlayable; /**< Is playable */ - int32_t bIsDisplayable; /**< Is displayable */ - int32_t bIsExecuteable; /**< Is executeable */ - int32_t bIsPrintable; /**< Is printable */ - T_DRM_Rights_Constraint PlayConstraint; /**< Play constraint */ - T_DRM_Rights_Constraint DisplayConstraint; /**< Display constraint */ - T_DRM_Rights_Constraint ExecuteConstraint; /**< Execute constraint */ - T_DRM_Rights_Constraint PrintConstraint; /**< Print constraint */ -} T_DRM_Rights; - -/** - * Input year and month, return how many days that month have - * \param year (in)Input the year - * \param month (in)Input the month - * \return - * -A positive integer, which is how many days that month have - * -When wrong input, return -1 - */ -int32_t drm_monthDays(int32_t year, int32_t month); - -/** - * Check whether the date and time is valid. - * \param year year of the date - * \param month month of the date - * \param day day of the date - * \param hour hour of the time - * \param min minute of the time - * \param sec second of the time - * \return - * -when it is a valid time, return 0 - * -when it is a invalid time, return -1 - */ -int32_t drm_checkDate(int32_t year, int32_t month, int32_t day, int32_t hour, int32_t min, int32_t sec); - -/** - * Parse the rights object include xml format and wbxml format data - * - * \param buffer (in)Input the DRM rights object data - * \param bufferLen (in)The buffer length - * \param format (in)Which format, xml or wbxml - * \param pRights (out)A structure pointer which save the rights information - * - * \return - * -TRUE, when success - * -FALSE, when failed - */ -int32_t drm_relParser(uint8_t* buffer, int32_t bufferLen, int32_t Format, T_DRM_Rights* pRights); - -#ifdef __cplusplus -} -#endif - -#endif /* __PARSER_REL_H__ */ diff --git a/media/libdrm/mobile1/include/xml/wbxml_tinyparser.h b/media/libdrm/mobile1/include/xml/wbxml_tinyparser.h deleted file mode 100644 index 1c40467d05e06c5931f5d2186c3b2c559a934f9a..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/xml/wbxml_tinyparser.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __WBXML_TINYPARSER_H__ -#define __WBXML_TINYPARSER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define REL_TAG_RIGHTS 0x05 -#define REL_TAG_CONTEXT 0x06 -#define REL_TAG_VERSION 0x07 -#define REL_TAG_UID 0x08 -#define REL_TAG_AGREEMENT 0x09 -#define REL_TAG_ASSET 0x0A -#define REL_TAG_KEYINFO 0x0B -#define REL_TAG_KEYVALUE 0x0C -#define REL_TAG_PERMISSION 0x0D -#define REL_TAG_PLAY 0x0E -#define REL_TAG_DISPLAY 0x0F -#define REL_TAG_EXECUTE 0x10 -#define REL_TAG_PRINT 0x11 -#define REL_TAG_CONSTRAINT 0x12 -#define REL_TAG_COUNT 0x13 -#define REL_TAG_DATETIME 0x14 -#define REL_TAG_START 0x15 -#define REL_TAG_END 0x16 -#define REL_TAG_INTERVAL 0x17 - -#define REL_CHECK_WBXML_HEADER(x) ((x != NULL) && (x[0] == 0x03) && (x[1] == 0x0E) && (x[2] == 0x6A)) - -#ifdef __cplusplus -} -#endif - -#endif /* __WBXML_TINYPARSER_H__ */ diff --git a/media/libdrm/mobile1/include/xml/xml_tinyParser.h b/media/libdrm/mobile1/include/xml/xml_tinyParser.h deleted file mode 100644 index 4ad65b8167b3c5b96cf83022e9a1cee8c5bd4180..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/include/xml/xml_tinyParser.h +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef __XML_TINYPARSER_H__ -#define __XML_TINYPARSER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define XML_DOM_PARSER -#define WBXML_DOM_PARSER -#define XML_DOM_CHECK_ENDTAG -#define XML_ENABLE_ERRNO -#define WBXML_OLD_VERSION /* for drm only */ - -#ifdef DEBUG_MODE -void XML_PrintMallocInfo(); -#endif /* DEBUG_MODE */ - -#define XML_TRUE 1 -#define XML_FALSE 0 -#define XML_EOF 0 -#define XML_TAG_START 0 -#define XML_TAG_END 1 -#define XML_TAG_SELF 2 - -#define XML_MAX_PROPERTY_LEN 256 -#define XML_MAX_ATTR_NAME_LEN 256 -#define XML_MAX_ATTR_VALUE_LEN 256 -#define XML_MAX_VALUE_LEN 256 - -#define XML_ERROR_OK 0 -#define XML_ERROR_BUFFER_NULL -1 -#define XML_ERROR_ATTR_NAME -2 -#define XML_ERROR_ATTR_MISSED_EQUAL -3 -#define XML_ERROR_PROPERTY_NAME -4 -#define XML_ERROR_ATTR_VALUE -5 -#define XML_ERROR_ENDTAG -6 -#define XML_ERROR_NO_SUCH_NODE -7 -#define XML_ERROR_PROPERTY_END -8 -#define XML_ERROR_VALUE -9 -#define XML_ERROR_NO_START_TAG -14 -#define XML_ERROR_NOVALUE -15 - -#define WBXML_ERROR_MISSED_CONTENT -10 -#define WBXML_ERROR_MBUINT32 -11 -#define WBXML_ERROR_MISSED_STARTTAG -12 -#define WBXML_ERROR_MISSED_ENDTAG -13 - -#ifdef XML_ENABLE_ERRNO -extern int32_t xml_errno; -#define XML_ERROR(x) do { xml_errno = x; } while (0) -#else /* XML_ENABLE_ERRNO */ -#define XML_ERROR -#endif /* XML_ENABLE_ERRNO */ - -#ifdef XML_DOM_PARSER -uint8_t *XML_DOM_getNode(uint8_t *buffer, const uint8_t *const node); -uint8_t *XML_DOM_getNodeValue(uint8_t *buffer, uint8_t *node, - uint8_t **value, int32_t *valueLen); - -uint8_t *XML_DOM_getValue(uint8_t *buffer, uint8_t **pValue, int32_t *valueLen); -uint8_t *XML_DOM_getAttr(uint8_t *buffer, uint8_t **pName, int32_t *nameLen, - uint8_t **pValue, int32_t *valueLen); - -uint8_t *XML_DOM_getNextNode(uint8_t *buffer, uint8_t **pNodeName, - int32_t *nodenameLen); - -uint8_t *XML_DOM_getTag(uint8_t *buffer, int32_t *tagLen, int32_t *tagType); -#endif /* XML_DOM_PARSER */ - -#ifdef WBXML_DOM_PARSER - -#define WBXML_WITH_ATTR 0x80 -#define WBXML_WITH_CONTENT 0x40 -#define WBXML_ATTR_END 0x01 -#define WBXML_CONTENT_END 0x01 - -#define WBXML_SWITCH_PAGE 0x00 -#define WBXML_STR_I 0x03 -#define WBXML_END 0x00 -#define WBXML_OPAUE 0xC3 -#define WBXML_STR_T 0x83 -#define WBXML_OPAQUE 0xC3 - -#define WBXML_GET_TAG(x) ((x) & 0x3F) /* get 6-digits */ -#define WBXML_HAS_ATTR(x) ((x) & WBXML_WITH_ATTR) -#define WBXML_HAS_CONTENT(x) ((x) & WBXML_WITH_CONTENT) - -typedef struct _WBXML { - uint8_t version; - uint8_t unUsed[3]; - uint32_t publicid; - uint32_t charset; - int32_t strTableLen; - uint8_t *strTable; - uint8_t *Content; - uint8_t *End; - uint8_t *curPtr; - int32_t depth; -} WBXML; - -typedef int32_t XML_BOOL; - -#ifdef WBXML_OLD_VERSION -uint8_t *WBXML_DOM_getNode(uint8_t *buffer, int32_t bufferLen, - uint8_t *node); -uint8_t *WBXML_DOM_getNodeValue(uint8_t *buffer, int32_t bufferLen, - uint8_t *node, - uint8_t **value, - int32_t *valueLen); -#endif /* WBXML_OLD_VERSION */ - -XML_BOOL WBXML_DOM_Init(WBXML * pWbxml, uint8_t *buffer, - int32_t bufferLen); -XML_BOOL WBXML_DOM_Eof(WBXML * pWbxml); -uint8_t WBXML_DOM_GetTag(WBXML * pWbxml); -uint8_t WBXML_DOM_GetChar(WBXML * pWbxml); -uint8_t WBXML_DOM_GetUIntVar(WBXML * pWbxml); -void WBXML_DOM_Rewind(WBXML * pWbxml); -void WBXML_DOM_Seek(WBXML * pWbxml, int32_t offset); -int32_t WBXML_GetUintVar(const uint8_t *const buffer, int32_t *len); - -#endif /* WBXML_DOM_PARSER */ - -#ifdef XML_TREE_STRUCTURE - -typedef struct _XML_TREE_ATTR XML_TREE_ATTR; -struct _XML_TREE_ATTR { - uint8_t name[XML_MAX_ATTR_VALUE_LEN]; - uint8_t value[XML_MAX_ATTR_VALUE_LEN]; - XML_TREE_ATTR *next; -}; - -typedef struct _XML_TREE XML_TREE; -struct _XML_TREE { - uint8_t tag[XML_MAX_PROPERTY_LEN]; - uint8_t value[XML_MAX_VALUE_LEN]; - XML_TREE_ATTR *attr; - XML_TREE_ATTR *last_attr; - XML_TREE *brother; - XML_TREE *last_brother; - XML_TREE *child; -}; - -XML_TREE *XML_makeTree(uint8_t **buf); -void XML_freeTree(XML_TREE * pTree); - -#endif /* XML_TREE_STRUCTURE */ - -#ifdef __cplusplus -} -#endif - -#endif /* __XML_TINYPARSER_H__ */ diff --git a/media/libdrm/mobile1/src/jni/drm1_jni.c b/media/libdrm/mobile1/src/jni/drm1_jni.c deleted file mode 100644 index 11353a78bca1dc9ace7c27a8ccb0a117a607d4a3..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/jni/drm1_jni.c +++ /dev/null @@ -1,1166 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -/** - * @file drm1_jni.c - * - * This file implement the Java Native Interface - * for supporting OMA DRM 1.0 - */ - -#include -#include -#include "log.h" -#include "JNIHelp.h" - - -#define MS_PER_SECOND 1000 /* Milliseconds per second */ -#define MS_PER_MINUTE 60 * MS_PER_SECOND /* Milliseconds per minute */ -#define MS_PER_HOUR 60 * MS_PER_MINUTE /* Milliseconds per hour */ -#define MS_PER_DAY 24 * MS_PER_HOUR /* Milliseconds per day */ - -#define SECONDS_PER_MINUTE 60 /* Seconds per minute*/ -#define SECONDS_PER_HOUR 60 * SECONDS_PER_MINUTE /* Seconds per hour */ -#define SECONDS_PER_DAY 24 * SECONDS_PER_HOUR /* Seconds per day */ - -#define DAY_PER_MONTH 30 /* Days per month */ -#define DAY_PER_YEAR 365 /* Days per year */ - -/** Nonzero if 'y' is a leap year, else zero. */ -#define leap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0) - -/** Number of leap years from 1970 to 'y' (not including 'y' itself). */ -#define nleap(y) (((y) - 1969) / 4 - ((y) - 1901) / 100 + ((y) - 1601) / 400) - -/** Accumulated number of days from 01-Jan up to start of current month. */ -static const int32_t ydays[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 -}; - -#define int64_const(s) (s) -#define int64_add(dst, s1, s2) ((void)((dst) = (s1) + (s2))) -#define int64_mul(dst, s1, s2) ((void)((dst) = (int64_t)(s1) * (int64_t)(s2))) - -/** - * DRM data structure - */ -typedef struct _DrmData { - /** - * The id of the DRM content. - */ - int32_t id; - - /** - * The pointer of JNI interface. - */ - JNIEnv* env; - - /** - * The pointer of DRM raw content InputStream object. - */ - jobject* pInData; - - /** - * The len of the InputStream object. - */ - int32_t len; - - /** - * The next DRM data. - */ - struct _DrmData *next; -} DrmData; - -/** The table to hold all the DRM data. */ -static DrmData *drmTable = NULL; - -/** - * Allocate a new item of DrmData. - * - * \return a pointer to a DrmData item if allocate successfully, - * otherwise return NULL - */ -static DrmData * newItem(void) -{ - DrmData *d = (DrmData *)malloc(sizeof(DrmData)); - - if (d != NULL) { - d->id = -1; - d->next = NULL; - } - - return d; -} - -/** - * Free the memory of the specified DrmData item d. - * - * \param d - a pointer to DrmData - */ -static void freeItem(DrmData *d) -{ - assert(d != NULL); - - free(d); -} - -/** - * Insert a DrmData item with given name into the head of - * the DrmData list. - * - * @param d - the pointer of the JNI interface - * @param pInData - the pointer of the DRM content InputStream object. - * - * @return JNI_DRM_SUCCESS if insert successfully, otherwise - * return JNI_DRM_FAILURE - */ -static int32_t addItem(DrmData* d) -{ - if (NULL == d) - return JNI_DRM_FAILURE; - - if (NULL == drmTable) { - drmTable = d; - return JNI_DRM_SUCCESS; - } - - d->next = drmTable; - drmTable = d; - - return JNI_DRM_SUCCESS; -} - -/** - * Get the item from the DrmData list by the specified - * id. - * - * @param p - the pointer of the DRM content InputStream object. - * - * @return a pointer to the DrmData item if find it successfuly, - * otherwise return NULL - */ -static DrmData * getItem(int32_t id) -{ - DrmData *d; - - if (NULL == drmTable) - return NULL; - - for (d = drmTable; d != NULL; d = d->next) { - if (id == d->id) - return d; - } - - return NULL; -} - -/** - * Remove the specified DrmData item d. - * - * @param p - the pointer of the DRM content InputStream object. - * - * @return JNI_DRM_SUCCESS if remove successfuly, - * otherwise return JNI_DRM_FAILURE - */ -static int32_t removeItem(int32_t id) -{ - DrmData *curItem, *preItem, *dstItem; - - if (NULL == drmTable) - return JNI_DRM_FAILURE; - - preItem = NULL; - for (curItem = drmTable; curItem != NULL; curItem = curItem->next) { - if (id == curItem->id) { - if (curItem == drmTable) - drmTable = curItem->next; - else - preItem->next = curItem->next; - - freeItem(curItem); - - return JNI_DRM_SUCCESS; - } - - preItem = curItem; - } - - return JNI_DRM_FAILURE; -} - - -static int32_t getInputStreamDataLength(int32_t handle) -{ - JNIEnv* env; - jobject* pInputStream; - int32_t len; - DrmData* p; - jclass cls; - jmethodID mid; - - p = (DrmData *)handle; - - if (NULL == p) - return 0; - - env = p->env; - pInputStream = p->pInData; - len = p->len; - - if (NULL == env || p->len <= 0 || NULL == pInputStream) - return 0; - - /* check the original InputStream is available or not */ - cls = (*env)->GetObjectClass(env, *pInputStream); - mid = (*env)->GetMethodID(env, cls, "available", "()I"); - (*env)->DeleteLocalRef(env, cls); - - if (NULL == mid) - return 0; - - if (0 > (*env)->CallIntMethod(env, *pInputStream, mid)) - return 0; - - return len; -} - -static int32_t readInputStreamData(int32_t handle, uint8_t* buf, int32_t bufLen) -{ - JNIEnv* env; - jobject* pInputStream; - int32_t len; - DrmData* p; - jclass cls; - jmethodID mid; - jbyteArray tmp; - int tmpLen; - jbyte* pNativeBuf; - - p = (DrmData *)handle; - - if (NULL == p || NULL == buf || bufLen <- 0) - return 0; - - env = p->env; - pInputStream = p->pInData; - len = p->len; - - if (NULL == env || p->len <= 0 || NULL == pInputStream) - return 0; - - cls = (*env)->GetObjectClass(env, *pInputStream); - mid = (*env)->GetMethodID(env, cls, "read", "([BII)I"); - tmp = (*env)->NewByteArray(env, bufLen); - bufLen = (*env)->CallIntMethod(env, *pInputStream, mid, tmp, 0, bufLen); - - (*env)->DeleteLocalRef(env, cls); - - if (-1 == bufLen) - return -1; - - pNativeBuf = (*env)->GetByteArrayElements(env, tmp, NULL); - memcpy(buf, pNativeBuf, bufLen); - (*env)->ReleaseByteArrayElements(env, tmp, pNativeBuf, 0); - (*env)->DeleteLocalRef(env, tmp); - - return bufLen; -} - -static const T_DRM_Rights_Info_Node *searchRightsObject(const jbyte* roId, const T_DRM_Rights_Info_Node* pRightsList) -{ - const T_DRM_Rights_Info_Node *pTmp; - - if (NULL == roId || NULL == pRightsList) - return NULL; - - pTmp = pRightsList; - - while (NULL != pTmp) { - if(0 == strcmp((char *)roId, (char *)pTmp->roInfo.roId)) - break; - pTmp = pTmp->next; - } - - return pTmp; -} - -/** - * Returns the difference in seconds between the given GMT time - * and 1970-01-01 00:00:00 GMT. - * - * \param year the year (since 1970) - * \param month the month (1 - 12) - * \param day the day (1 - 31) - * \param hour the hour (0 - 23) - * \param minute the minute (0 - 59) - * \param second the second (0 - 59) - * - * \return the difference in seconds between the given GMT time - * and 1970-01-01 00:00:00 GMT. - */ -static int64_t mkgmtime( - uint32_t year, uint32_t month, uint32_t day, - uint32_t hour, uint32_t minute, uint32_t second) -{ - int64_t result; - - /* - * FIXME: It does not check whether the specified days - * is valid based on the specified months. - */ - assert(year >= 1970 - && month > 0 && month <= 12 - && day > 0 && day <= 31 - && hour < 24 && minute < 60 - && second < 60); - - /* Set 'day' to the number of days into the year. */ - day += ydays[month - 1] + (month > 2 && leap (year)) - 1; - - /* Now calculate 'day' to the number of days since Jan 1, 1970. */ - day = day + 365 * (year - 1970) + nleap(year); - - int64_mul(result, int64_const(day), int64_const(SECONDS_PER_DAY)); - int64_add(result, result, int64_const( - SECONDS_PER_HOUR * hour + SECONDS_PER_MINUTE * minute + second)); - - return result; -} - -/** - * Compute the milliseconds by the specified date - * and time. - * - * @param date - the specified date, - * date = year * 10000 + month * 100 + day - * @param time - the specified time, - * time = hour * 10000 + minute * 100 + second - * - * @return the related milliseconds - */ -static int64_t computeTime(int32_t date, int32_t time) -{ - int32_t year, month, day, hour, minute, second; - - year = date / 10000; - month = (date / 100) % 100; - day = date % 100; - hour = time / 10000; - minute = (time / 100) % 100; - second = time % 100; - - /* Adjust the invalid parameters. */ - if (year < 1970) year = 1970; - if (month < 1) month = 1; - if (month > 12) month = 12; - if (day < 1) day = 1; - if (day > 31) day = 31; - if (hour < 0) hour = 0; - if (hour > 23) hour = 23; - if (minute < 0) minute = 0; - if (minute > 59) minute = 59; - if (second < 0) second = 0; - if (second > 59) second = 59; - - return mkgmtime(year, month, day, hour, minute, second) * 1000; -} - -/** - * Compute the milliseconds by the specified date - * and time. - * Note that here we always treat 1 year as 365 days and 1 month as 30 days - * that is not precise. But it should not be a problem since OMA DRM 2.0 - * already restricts the interval representation to be day-based, - * i.e. there will not be an interval with year or month any more in the - * future. - * - * @param date - the specified date, - * date = year * 10000 + month * 100 + day - * @param time - the specified time, - * time = hour * 10000 + minute * 100 + second - * - * @return the related milliseconds - */ -static int64_t computeInterval(int32_t date, int32_t time) -{ - int32_t year, month, day, hour, minute, second; - int64_t milliseconds; - - year = date / 10000; - month = (date / 100) % 100; - day = date % 100; - hour = time / 10000; - minute = (time / 100) % 100; - second = time % 100; - - /* milliseconds = ((((year * 365 + month * 30 + day) * 24 - * + hour) * 60 + minute) * 60 + second) * 1000; - */ - int64_mul(milliseconds, - int64_const(year * DAY_PER_YEAR + month * DAY_PER_MONTH + day), - int64_const(MS_PER_DAY)); - int64_add(milliseconds, milliseconds, - int64_const(hour * MS_PER_HOUR + minute * MS_PER_MINUTE + - second * MS_PER_SECOND)); - - return milliseconds; -} - -static jint getObjectIntField(JNIEnv * env, jobject obj, const char *name, jint * value) -{ - jclass clazz; - jfieldID field; - - clazz = (*env)->GetObjectClass(env, obj); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, name, "I"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - *value = (*env)->GetIntField(env, obj, field); - - return JNI_DRM_SUCCESS; -} - -static jint setObjectIntField(JNIEnv * env, jobject obj, const char *name, jint value) -{ - jclass clazz; - jfieldID field; - - clazz = (*env)->GetObjectClass(env, obj); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, name, "I"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - (*env)->SetIntField(env, obj, field, value); - - return JNI_DRM_SUCCESS; -} - -static jint setObjectLongField(JNIEnv * env, jobject obj, const char *name, jlong value) -{ - jclass clazz; - jfieldID field; - - clazz = (*env)->GetObjectClass(env, obj); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, name, "J"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - (*env)->SetLongField(env, obj, field, value); - - return JNI_DRM_SUCCESS; -} - -static jint setConstraintFields(JNIEnv * env, jobject constraint, T_DRM_Constraint_Info * pConstraint) -{ - /* if no this permission */ - if (pConstraint->indicator == (uint8_t)DRM_NO_RIGHTS) { - if (JNI_DRM_FAILURE == setObjectIntField(env, constraint, "count", 0)) - return JNI_DRM_FAILURE; - - return JNI_DRM_SUCCESS; - } - - /* set count field */ - if (pConstraint->indicator & DRM_COUNT_CONSTRAINT) { - if (JNI_DRM_FAILURE == setObjectIntField(env, constraint, "count", pConstraint->count)) - return JNI_DRM_FAILURE; - } - - /* set start time field */ - if (pConstraint->indicator & DRM_START_TIME_CONSTRAINT) { - int64_t startTime; - - startTime = computeTime(pConstraint->startDate, pConstraint->startTime); - - if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "startDate", startTime)) - return JNI_DRM_FAILURE; - } - - /* set end time field */ - if (pConstraint->indicator & DRM_END_TIME_CONSTRAINT) { - int64_t endTime; - - endTime = computeTime(pConstraint->endDate, pConstraint->endTime); - - if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "endDate", endTime)) - return JNI_DRM_FAILURE; - } - - /* set interval field */ - if (pConstraint->indicator & DRM_INTERVAL_CONSTRAINT) { - int64_t interval; - - interval = computeInterval(pConstraint->intervalDate, pConstraint->intervalTime); - - if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "interval", interval)) - return JNI_DRM_FAILURE; - } - - return JNI_DRM_SUCCESS; -} - -static jint setRightsFields(JNIEnv * env, jobject rights, T_DRM_Rights_Info* pRoInfo) -{ - jclass clazz; - jfieldID field; - jstring str; - jint index; - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - /* set roId field */ - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->NewStringUTF(env, (char *)pRoInfo->roId); - if (NULL == str) - return JNI_DRM_FAILURE; - - (*env)->SetObjectField(env, rights, field, str); - (*env)->DeleteLocalRef(env, str); - - return JNI_DRM_SUCCESS; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent - (JNIEnv * env, jobject rawContent, jobject data, jint len, jint mimeType) -{ - int32_t id; - T_DRM_Input_Data inData; - DrmData* drmInData; - - switch (mimeType) { - case JNI_DRM_MIMETYPE_MESSAGE: - mimeType = TYPE_DRM_MESSAGE; - break; - case JNI_DRM_MIMETYPE_CONTENT: - mimeType = TYPE_DRM_CONTENT; - break; - default: - return JNI_DRM_FAILURE; - } - - drmInData = newItem(); - if (NULL == drmInData) - return JNI_DRM_FAILURE; - - drmInData->env = env; - drmInData->pInData = &data; - drmInData->len = len; - - if (JNI_DRM_FAILURE == addItem(drmInData)) - return JNI_DRM_FAILURE; - - inData.inputHandle = (int32_t)drmInData; - inData.mimeType = mimeType; - inData.getInputDataLength = getInputStreamDataLength; - inData.readInputData = readInputStreamData; - - id = SVC_drm_openSession(inData); - if (id < 0) - return JNI_DRM_FAILURE; - - drmInData->id = id; - - return id; -} - -/* native interface */ -JNIEXPORT jstring JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress - (JNIEnv * env, jobject rawContent) -{ - jint id; - uint8_t rightsIssuer[256] = {0}; - jstring str = NULL; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return NULL; - - if (DRM_SUCCESS == SVC_drm_getRightsIssuer(id, rightsIssuer)) - str = (*env)->NewStringUTF(env, (char *)rightsIssuer); - - return str; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod - (JNIEnv * env, jobject rawContent) -{ - jint id; - int32_t res; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - res = SVC_drm_getDeliveryMethod(id); - - switch (res) { - case FORWARD_LOCK: - return JNI_DRM_FORWARD_LOCK; - case COMBINED_DELIVERY: - return JNI_DRM_COMBINED_DELIVERY; - case SEPARATE_DELIVERY: - return JNI_DRM_SEPARATE_DELIVERY; - case SEPARATE_DELIVERY_FL: - return JNI_DRM_SEPARATE_DELIVERY_DM; - default: - return JNI_DRM_FAILURE; - } -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeReadContent - (JNIEnv * env, jobject rawContent, jbyteArray buf, jint bufOff, jint len, jint mediaOff) -{ - jint id; - jbyte *nativeBuf; - jclass cls; - jmethodID mid; - DrmData* p; - jobject inputStream; - jfieldID field; - - if (NULL == buf) { - jniThrowNullPointerException(env, "b == null"); - return JNI_DRM_FAILURE; - } - - if (len < 0 || bufOff < 0 || len + bufOff > (*env)->GetArrayLength(env, buf)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL); - return JNI_DRM_FAILURE; - } - - if (mediaOff < 0 || len == 0) - return JNI_DRM_FAILURE; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - p = getItem(id); - if (NULL == p) - return JNI_DRM_FAILURE; - - cls = (*env)->GetObjectClass(env, rawContent); - if (NULL == cls) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, cls, "inData", "Ljava/io/BufferedInputStream;"); - (*env)->DeleteLocalRef(env, cls); - - if (NULL == field) - return JNI_DRM_FAILURE; - - inputStream = (*env)->GetObjectField(env, rawContent, field); - - p->env = env; - p->pInData = &inputStream; - - nativeBuf = (*env)->GetByteArrayElements(env, buf, NULL); - - len = SVC_drm_getContent(id, mediaOff, (uint8_t *)nativeBuf + bufOff, len); - - (*env)->ReleaseByteArrayElements(env, buf, nativeBuf, 0); - - if (DRM_MEDIA_EOF == len) - return JNI_DRM_EOF; - if (len <= 0) - return JNI_DRM_FAILURE; - - return len; -} - -/* native interface */ -JNIEXPORT jstring JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetContentType - (JNIEnv * env, jobject rawContent) -{ - jint id; - uint8_t contentType[64] = {0}; - jstring str = NULL; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return NULL; - - if (DRM_SUCCESS == SVC_drm_getContentType(id, contentType)) - str = (*env)->NewStringUTF(env, (char *)contentType); - - return str; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength - (JNIEnv * env, jobject rawContent) -{ - jint id; - int32_t len; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - len = SVC_drm_getContentLength(id); - - if (DRM_UNKNOWN_DATA_LEN == len) - return JNI_DRM_UNKNOWN_DATA_LEN; - - if (0 > len) - return JNI_DRM_FAILURE; - - return len; -} - -/* native interface */ -JNIEXPORT void JNICALL -Java_android_drm_mobile1_DrmRawContent_finalize - (JNIEnv * env, jobject rawContent) -{ - jint id; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return; - - removeItem(id); - - SVC_drm_closeSession(id); -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo - (JNIEnv * env, jobject rights, jint permission, jobject constraint) -{ - jclass clazz; - jfieldID field; - jstring str; - uint8_t *nativeStr; - T_DRM_Rights_Info_Node *pRightsList; - T_DRM_Rights_Info_Node *pCurNode; - T_DRM_Constraint_Info *pConstraint; - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->GetObjectField(env, rights, field); - - nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL); - if (NULL == nativeStr) - return JNI_DRM_FAILURE; - - /* this means forward-lock rights */ - if (0 == strcmp((char *)nativeStr, "ForwardLock")) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_SUCCESS; - } - - if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_FAILURE; - } - - pCurNode = searchRightsObject((jbyte *)nativeStr, pRightsList); - if (NULL == pCurNode) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - SVC_drm_freeRightsInfoList(pRightsList); - return JNI_DRM_FAILURE; - } - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - - switch (permission) { - case JNI_DRM_PERMISSION_PLAY: - pConstraint = &(pCurNode->roInfo.playRights); - break; - case JNI_DRM_PERMISSION_DISPLAY: - pConstraint = &(pCurNode->roInfo.displayRights); - break; - case JNI_DRM_PERMISSION_EXECUTE: - pConstraint = &(pCurNode->roInfo.executeRights); - break; - case JNI_DRM_PERMISSION_PRINT: - pConstraint = &(pCurNode->roInfo.printRights); - break; - default: - SVC_drm_freeRightsInfoList(pRightsList); - return JNI_DRM_FAILURE; - } - - /* set constraint field */ - if (JNI_DRM_FAILURE == setConstraintFields(env, constraint, pConstraint)) { - SVC_drm_freeRightsInfoList(pRightsList); - return JNI_DRM_FAILURE; - } - - SVC_drm_freeRightsInfoList(pRightsList); - - return JNI_DRM_SUCCESS; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRights_nativeConsumeRights - (JNIEnv * env, jobject rights, jint permission) -{ - jclass clazz; - jfieldID field; - jstring str; - uint8_t *nativeStr; - int32_t id; - - switch (permission) { - case JNI_DRM_PERMISSION_PLAY: - permission = DRM_PERMISSION_PLAY; - break; - case JNI_DRM_PERMISSION_DISPLAY: - permission = DRM_PERMISSION_DISPLAY; - break; - case JNI_DRM_PERMISSION_EXECUTE: - permission = DRM_PERMISSION_EXECUTE; - break; - case JNI_DRM_PERMISSION_PRINT: - permission = DRM_PERMISSION_PRINT; - break; - default: - return JNI_DRM_FAILURE; - } - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->GetObjectField(env, rights, field); - - nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL); - if (NULL == nativeStr) - return JNI_DRM_FAILURE; - - if (0 == strcmp("ForwardLock", (char *)nativeStr)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_SUCCESS; - } - - if (DRM_SUCCESS != SVC_drm_updateRights(nativeStr, permission)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_FAILURE; - } - - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - - return JNI_DRM_SUCCESS; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights - (JNIEnv * env, jobject rightsManager, jobject data, jint len, jint mimeType, jobject rights) -{ - int32_t id; - T_DRM_Input_Data inData; - DrmData* drmInData; - jclass cls; - jmethodID mid; - T_DRM_Rights_Info rightsInfo; - - switch (mimeType) { - case JNI_DRM_MIMETYPE_RIGHTS_XML: - mimeType = TYPE_DRM_RIGHTS_XML; - break; - case JNI_DRM_MIMETYPE_RIGHTS_WBXML: - mimeType = TYPE_DRM_RIGHTS_WBXML; - break; - case JNI_DRM_MIMETYPE_MESSAGE: - mimeType = TYPE_DRM_MESSAGE; - break; - default: - return JNI_DRM_FAILURE; - } - - drmInData = newItem(); - if (NULL == drmInData) - return JNI_DRM_FAILURE; - - drmInData->env = env; - drmInData->pInData = &data; - drmInData->len = len; - - inData.inputHandle = (int32_t)drmInData; - inData.mimeType = mimeType; - inData.getInputDataLength = getInputStreamDataLength; - inData.readInputData = readInputStreamData; - - memset(&rightsInfo, 0, sizeof(T_DRM_Rights_Info)); - if (DRM_FAILURE == SVC_drm_installRights(inData, &rightsInfo)) - return JNI_DRM_FAILURE; - - freeItem(drmInData); - - return setRightsFields(env, rights, &rightsInfo); -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights - (JNIEnv * env, jobject rightsManager, jobject rawContent, jobject rights) -{ - jint id; - T_DRM_Rights_Info rightsInfo; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - memset(&rightsInfo, 0, sizeof(T_DRM_Rights_Info)); - if (DRM_SUCCESS != SVC_drm_getRightsInfo(id, &rightsInfo)) - return JNI_DRM_FAILURE; - - return setRightsFields(env, rights, &rightsInfo); -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights - (JNIEnv * env, jobject rightsManager) -{ - T_DRM_Rights_Info_Node *pRightsList; - T_DRM_Rights_Info_Node *pCurNode; - int32_t num = 0; - - if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList)) - return JNI_DRM_FAILURE; - - pCurNode = pRightsList; - while (pCurNode != NULL) { - num++; - pCurNode = pCurNode->next; - } - - SVC_drm_freeRightsInfoList(pRightsList); - - return num; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList - (JNIEnv * env, jobject rightsManager, jobjectArray rightsArray, jint num) -{ - T_DRM_Rights_Info_Node *pRightsList; - T_DRM_Rights_Info_Node *pCurNode; - int32_t index; - - if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList)) - return JNI_DRM_FAILURE; - - pCurNode = pRightsList; - for (index = 0; NULL != pCurNode; index++) { - jobject rights = (*env)->GetObjectArrayElement(env, rightsArray, index); - if (NULL == rights) - break; - - if (JNI_DRM_FAILURE == setRightsFields(env, rights, &(pCurNode->roInfo))) - break; - - (*env)->SetObjectArrayElement(env, rightsArray, index, rights); - - pCurNode = pCurNode->next; - } - - SVC_drm_freeRightsInfoList(pRightsList); - - return index; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights - (JNIEnv * env, jobject rightsManager, jobject rights) -{ - jclass clazz; - jfieldID field; - jstring str; - uint8_t *nativeStr; - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->GetObjectField(env, rights, field); - - nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL); - if (NULL == nativeStr) - return JNI_DRM_FAILURE; - - if (0 == strcmp("ForwardLock", (char *)nativeStr)) - return JNI_DRM_SUCCESS; - - if (DRM_SUCCESS != SVC_drm_deleteRights(nativeStr)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_FAILURE; - } - - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_SUCCESS; -} - -/* - * Table of methods associated with the DrmRawContent class. - */ -static JNINativeMethod gDrmRawContentMethods[] = { - /* name, signature, funcPtr */ - {"nativeConstructDrmContent", "(Ljava/io/InputStream;II)I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent}, - {"nativeGetRightsAddress", "()Ljava/lang/String;", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress}, - {"nativeGetDeliveryMethod", "()I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod}, - {"nativeReadContent", "([BIII)I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeReadContent}, - {"nativeGetContentType", "()Ljava/lang/String;", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetContentType}, - {"nativeGetContentLength", "()I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength}, - {"finalize", "()V", - (void*)Java_android_drm_mobile1_DrmRawContent_finalize}, -}; - -/* - * Table of methods associated with the DrmRights class. - */ -static JNINativeMethod gDrmRightsMethods[] = { - /* name, signature, funcPtr */ - {"nativeGetConstraintInfo", "(ILandroid/drm/mobile1/DrmConstraintInfo;)I", - (void*)Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo}, - {"nativeConsumeRights", "(I)I", - (void*)Java_android_drm_mobile1_DrmRights_nativeConsumeRights}, -}; - -/* - * Table of methods associated with the DrmRightsManager class. - */ -static JNINativeMethod gDrmRightsManagerMethods[] = { - /* name, signature, funcPtr */ - {"nativeInstallDrmRights", "(Ljava/io/InputStream;IILandroid/drm/mobile1/DrmRights;)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights}, - {"nativeQueryRights", "(Landroid/drm/mobile1/DrmRawContent;Landroid/drm/mobile1/DrmRights;)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights}, - {"nativeGetNumOfRights", "()I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights}, - {"nativeGetRightsList", "([Landroid/drm/mobile1/DrmRights;I)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList}, - {"nativeDeleteRights", "(Landroid/drm/mobile1/DrmRights;)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights}, -}; - -/* - * Register several native methods for one class. - */ -static int registerNativeMethods(JNIEnv* env, const char* className, - JNINativeMethod* gMethods, int numMethods) -{ - jclass clazz; - - clazz = (*env)->FindClass(env, className); - if (clazz == NULL) - return JNI_FALSE; - - if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) - return JNI_FALSE; - - return JNI_TRUE; -} - -/* - * Register native methods for all classes we know about. - */ -static int registerNatives(JNIEnv* env) -{ - if (!registerNativeMethods(env, "android/drm/mobile1/DrmRawContent", - gDrmRawContentMethods, sizeof(gDrmRawContentMethods) / sizeof(gDrmRawContentMethods[0]))) - return JNI_FALSE; - - if (!registerNativeMethods(env, "android/drm/mobile1/DrmRights", - gDrmRightsMethods, sizeof(gDrmRightsMethods) / sizeof(gDrmRightsMethods[0]))) - return JNI_FALSE; - - if (!registerNativeMethods(env, "android/drm/mobile1/DrmRightsManager", - gDrmRightsManagerMethods, sizeof(gDrmRightsManagerMethods) / sizeof(gDrmRightsManagerMethods[0]))) - return JNI_FALSE; - - return JNI_TRUE; -} - -jint JNI_OnLoad(JavaVM* vm, void* reserved) -{ - JNIEnv* env = NULL; - jint result = -1; - - printf("Entering JNI_OnLoad\n"); - - if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) - goto bail; - - assert(env != NULL); - - if (!registerNatives(env)) - goto bail; - - /* success -- return valid version number */ - result = JNI_VERSION_1_4; - -bail: - printf("Leaving JNI_OnLoad (result=0x%x)\n", result); - return result; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_api.c b/media/libdrm/mobile1/src/objmng/drm_api.c deleted file mode 100644 index 232d9f44a1c3b6943063caecca4d21659aa228a5..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/objmng/drm_api.c +++ /dev/null @@ -1,1943 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "log.h" - -/** - * Current id. - */ -static int32_t curID = 0; - -/** - * The header pointer for the session list. - */ -static T_DRM_Session_Node* sessionTable = NULL; - -/** - * New a session. - */ -static T_DRM_Session_Node* newSession(T_DRM_Input_Data data) -{ - T_DRM_Session_Node* s = (T_DRM_Session_Node *)malloc(sizeof(T_DRM_Session_Node)); - - if (NULL != s) { - memset(s, 0, sizeof(T_DRM_Session_Node)); - - s->sessionId = curID++; - s->inputHandle = data.inputHandle; - s->mimeType = data.mimeType; - s->getInputDataLengthFunc = data.getInputDataLength; - s->readInputDataFunc = data.readInputData; - s->seekInputDataFunc = data.seekInputData; - } - - return s; -} - -/** - * Free a session. - */ -static void freeSession(T_DRM_Session_Node* s) -{ - if (NULL == s) - return; - - if (NULL != s->rawContent) - free(s->rawContent); - - if (NULL != s->readBuf) - free(s->readBuf); - - if (NULL != s->infoStruct) - free(s->infoStruct); - - free(s); -} - -/** - * Add a session to list. - */ -static int32_t addSession(T_DRM_Session_Node* s) -{ - if (NULL == s) - return -1; - - s->next = sessionTable; - sessionTable = s; - - return s->sessionId; -} - -/** - * Get a session from the list. - */ -static T_DRM_Session_Node* getSession(int32_t sessionId) -{ - T_DRM_Session_Node* s; - - if (sessionId < 0 || NULL == sessionTable) - return NULL; - - for (s = sessionTable; s != NULL; s = s->next) { - if (sessionId == s->sessionId) - return s; - } - - return NULL; -} - -/** - * Remove a session from the list. - */ -static void removeSession(int32_t sessionId) -{ - T_DRM_Session_Node *curS, *preS; - - if (sessionId < 0 || NULL == sessionTable) - return; - - if (sessionId == sessionTable->sessionId) { - curS = sessionTable; - sessionTable = curS->next; - freeSession(curS); - return; - } - - for (preS = sessionTable; preS->next != NULL; preS = preS->next) { - if (preS->next->sessionId == sessionId) - curS = preS->next; - } - - if (NULL == preS->next) - return; - - preS->next = curS->next; - freeSession(curS); -} - -/** - * Try to identify the mimetype according the input DRM data. - */ -static int32_t getMimeType(const uint8_t *buf, int32_t bufLen) -{ - const uint8_t *p; - - if (NULL == buf || bufLen <= 0) - return TYPE_DRM_UNKNOWN; - - p = buf; - - /* check if it is DRM Content Format, only check the first field of Version, it must be "0x01" */ - if (0x01 == *p) - return TYPE_DRM_CONTENT; - - /* check if it is DRM Message, only check the first two bytes, it must be the start flag of boundary: "--" */ - if (bufLen >= 2 && '-' == *p && '-' == *(p + 1)) - return TYPE_DRM_MESSAGE; - - /* check if it is DRM Rights XML format, only check the first several bytes, it must be: "= 12 && 0 == strncmp("= 2 && 0x03 == *p && 0x0e == *(p + 1)) - return TYPE_DRM_RIGHTS_WBXML; - - return TYPE_DRM_UNKNOWN; -} - -static int32_t drm_skipCRLFinB64(const uint8_t* b64Data, int32_t len) -{ - const uint8_t* p; - int32_t skipLen = 0; - - if (NULL == b64Data || len <= 0) - return -1; - - p = b64Data; - while (p - b64Data < len) { - if ('\r' == *p || '\n'== *p) - skipLen++; - p++; - } - - return skipLen; -} - -static int32_t drm_scanEndBoundary(const uint8_t* pBuf, int32_t len, uint8_t* const boundary) -{ - const uint8_t* p; - int32_t leftLen; - int32_t boundaryLen; - - if (NULL == pBuf || len <=0 || NULL == boundary) - return -1; - - p = pBuf; - boundaryLen = strlen((char *)boundary) + 2; /* 2 means: '\r' and '\n' */ - leftLen = len - (p - pBuf); - while (leftLen > 0) { - if (NULL == (p = memchr(p, '\r', leftLen))) - break; - - leftLen = len - (p - pBuf); - if (leftLen < boundaryLen) - return -2; /* here means may be the boundary has been split */ - - if (('\n' == *(p + 1)) && (0 == memcmp(p + 2, boundary, strlen((char *)boundary)))) - return p - pBuf; /* find the boundary here */ - - p++; - leftLen--; - } - - return len; /* no boundary found */ -} - -static int32_t drm_getLicenseInfo(T_DRM_Rights* pRights, T_DRM_Rights_Info* licenseInfo) -{ - if (NULL != licenseInfo && NULL != pRights) { - strcpy((char *)licenseInfo->roId, (char *)pRights->uid); - - if (1 == pRights->bIsDisplayable) { - licenseInfo->displayRights.indicator = pRights->DisplayConstraint.Indicator; - licenseInfo->displayRights.count = - pRights->DisplayConstraint.Count; - licenseInfo->displayRights.startDate = - pRights->DisplayConstraint.StartTime.date; - licenseInfo->displayRights.startTime = - pRights->DisplayConstraint.StartTime.time; - licenseInfo->displayRights.endDate = - pRights->DisplayConstraint.EndTime.date; - licenseInfo->displayRights.endTime = - pRights->DisplayConstraint.EndTime.time; - licenseInfo->displayRights.intervalDate = - pRights->DisplayConstraint.Interval.date; - licenseInfo->displayRights.intervalTime = - pRights->DisplayConstraint.Interval.time; - } - if (1 == pRights->bIsPlayable) { - licenseInfo->playRights.indicator = pRights->PlayConstraint.Indicator; - licenseInfo->playRights.count = pRights->PlayConstraint.Count; - licenseInfo->playRights.startDate = - pRights->PlayConstraint.StartTime.date; - licenseInfo->playRights.startTime = - pRights->PlayConstraint.StartTime.time; - licenseInfo->playRights.endDate = - pRights->PlayConstraint.EndTime.date; - licenseInfo->playRights.endTime = - pRights->PlayConstraint.EndTime.time; - licenseInfo->playRights.intervalDate = - pRights->PlayConstraint.Interval.date; - licenseInfo->playRights.intervalTime = - pRights->PlayConstraint.Interval.time; - } - if (1 == pRights->bIsExecuteable) { - licenseInfo->executeRights.indicator = pRights->ExecuteConstraint.Indicator; - licenseInfo->executeRights.count = - pRights->ExecuteConstraint.Count; - licenseInfo->executeRights.startDate = - pRights->ExecuteConstraint.StartTime.date; - licenseInfo->executeRights.startTime = - pRights->ExecuteConstraint.StartTime.time; - licenseInfo->executeRights.endDate = - pRights->ExecuteConstraint.EndTime.date; - licenseInfo->executeRights.endTime = - pRights->ExecuteConstraint.EndTime.time; - licenseInfo->executeRights.intervalDate = - pRights->ExecuteConstraint.Interval.date; - licenseInfo->executeRights.intervalTime = - pRights->ExecuteConstraint.Interval.time; - } - if (1 == pRights->bIsPrintable) { - licenseInfo->printRights.indicator = pRights->PrintConstraint.Indicator; - licenseInfo->printRights.count = - pRights->PrintConstraint.Count; - licenseInfo->printRights.startDate = - pRights->PrintConstraint.StartTime.date; - licenseInfo->printRights.startTime = - pRights->PrintConstraint.StartTime.time; - licenseInfo->printRights.endDate = - pRights->PrintConstraint.EndTime.date; - licenseInfo->printRights.endTime = - pRights->PrintConstraint.EndTime.time; - licenseInfo->printRights.intervalDate = - pRights->PrintConstraint.Interval.date; - licenseInfo->printRights.intervalTime = - pRights->PrintConstraint.Interval.time; - } - return TRUE; - } - return FALSE; -} - -static int32_t drm_addRightsNodeToList(T_DRM_Rights_Info_Node **ppRightsHeader, - T_DRM_Rights_Info_Node *pInputRightsNode) -{ - T_DRM_Rights_Info_Node *pRightsNode; - - if (NULL == ppRightsHeader || NULL == pInputRightsNode) - return FALSE; - - pRightsNode = (T_DRM_Rights_Info_Node *)malloc(sizeof(T_DRM_Rights_Info_Node)); - if (NULL == pRightsNode) - return FALSE; - - memcpy(pRightsNode, pInputRightsNode, sizeof(T_DRM_Rights_Info_Node)); - pRightsNode->next = NULL; - - /* this means it is the first node */ - if (NULL == *ppRightsHeader) - *ppRightsHeader = pRightsNode; - else { - T_DRM_Rights_Info_Node *pTmp; - - pTmp = *ppRightsHeader; - while (NULL != pTmp->next) - pTmp = pTmp->next; - - pTmp->next = pRightsNode; - } - return TRUE; -} - -static int32_t drm_startConsumeRights(int32_t * bIsXXable, - T_DRM_Rights_Constraint * XXConstraint, - int32_t * writeFlag) -{ - T_DB_TIME_SysTime curDateTime; - T_DRM_DATETIME CurrentTime; - uint8_t countFlag = 0; - - memset(&CurrentTime, 0, sizeof(T_DRM_DATETIME)); - - if (NULL == bIsXXable || 0 == *bIsXXable || NULL == XXConstraint || NULL == writeFlag) - return DRM_FAILURE; - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_NO_CONSTRAINT)) /* Have utter right? */ - return DRM_SUCCESS; - - *bIsXXable = 0; /* Assume have invalid rights at first */ - *writeFlag = 0; - - if (0 != (XXConstraint->Indicator & (DRM_START_TIME_CONSTRAINT | DRM_END_TIME_CONSTRAINT | DRM_INTERVAL_CONSTRAINT))) { - DRM_time_getSysTime(&curDateTime); - - if (-1 == drm_checkDate(curDateTime.year, curDateTime.month, curDateTime.day, - curDateTime.hour, curDateTime.min, curDateTime.sec)) - return DRM_FAILURE; - - YMD_HMS_2_INT(curDateTime.year, curDateTime.month, curDateTime.day, - CurrentTime.date, curDateTime.hour, curDateTime.min, - curDateTime.sec, CurrentTime.time); - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_COUNT_CONSTRAINT)) { /* Have count restrict? */ - *writeFlag = 1; - /* If it has only one time for use, after use this function, we will delete this rights */ - if (XXConstraint->Count <= 0) { - XXConstraint->Indicator &= ~DRM_COUNT_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - - if (XXConstraint->Count-- <= 1) { - XXConstraint->Indicator &= ~DRM_COUNT_CONSTRAINT; - countFlag = 1; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_START_TIME_CONSTRAINT)) { - if (XXConstraint->StartTime.date > CurrentTime.date || - (XXConstraint->StartTime.date == CurrentTime.date && - XXConstraint->StartTime.time >= CurrentTime.time)) { - *bIsXXable = 1; - return DRM_RIGHTS_PENDING; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_END_TIME_CONSTRAINT)) { /* Have end time restrict? */ - if (XXConstraint->EndTime.date < CurrentTime.date || - (XXConstraint->EndTime.date == CurrentTime.date && - XXConstraint->EndTime.time <= CurrentTime.time)) { - *writeFlag = 1; - XXConstraint->Indicator &= ~DRM_END_TIME_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_INTERVAL_CONSTRAINT)) { /* Have interval time restrict? */ - int32_t year, mon, day, hour, min, sec, date, time; - int32_t ret; - - XXConstraint->Indicator |= DRM_END_TIME_CONSTRAINT; - XXConstraint->Indicator &= ~DRM_INTERVAL_CONSTRAINT; /* Write off interval right */ - *writeFlag = 1; - - if (XXConstraint->Interval.date == 0 - && XXConstraint->Interval.time == 0) { - return DRM_RIGHTS_EXPIRED; - } - date = CurrentTime.date + XXConstraint->Interval.date; - time = CurrentTime.time + XXConstraint->Interval.time; - INT_2_YMD_HMS(year, mon, day, date, hour, min, sec, time); - - if (sec > 59) { - min += sec / 60; - sec %= 60; - } - if (min > 59) { - hour += min / 60; - min %= 60; - } - if (hour > 23) { - day += hour / 24; - hour %= 24; - } - if (day > 31) { - mon += day / 31; - day %= 31; - } - if (mon > 12) { - year += mon / 12; - mon %= 12; - } - if (day > (ret = drm_monthDays(year, mon))) { - day -= ret; - mon++; - if (mon > 12) { - mon -= 12; - year++; - } - } - YMD_HMS_2_INT(year, mon, day, XXConstraint->EndTime.date, hour, - min, sec, XXConstraint->EndTime.time); - } - - if (1 != countFlag) - *bIsXXable = 1; /* Can go here ,so right must be valid */ - return DRM_SUCCESS; -} - -static int32_t drm_startCheckRights(int32_t * bIsXXable, - T_DRM_Rights_Constraint * XXConstraint) -{ - T_DB_TIME_SysTime curDateTime; - T_DRM_DATETIME CurrentTime; - - memset(&CurrentTime, 0, sizeof(T_DRM_DATETIME)); - - if (NULL == bIsXXable || 0 == *bIsXXable || NULL == XXConstraint) - return DRM_FAILURE; - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_NO_CONSTRAINT)) /* Have utter right? */ - return DRM_SUCCESS; - - *bIsXXable = 0; /* Assume have invalid rights at first */ - - if (0 != (XXConstraint->Indicator & (DRM_START_TIME_CONSTRAINT | DRM_END_TIME_CONSTRAINT))) { - DRM_time_getSysTime(&curDateTime); - - if (-1 == drm_checkDate(curDateTime.year, curDateTime.month, curDateTime.day, - curDateTime.hour, curDateTime.min, curDateTime.sec)) - return DRM_FAILURE; - - YMD_HMS_2_INT(curDateTime.year, curDateTime.month, curDateTime.day, - CurrentTime.date, curDateTime.hour, curDateTime.min, - curDateTime.sec, CurrentTime.time); - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_COUNT_CONSTRAINT)) { /* Have count restrict? */ - if (XXConstraint->Count <= 0) { - XXConstraint->Indicator &= ~DRM_COUNT_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_START_TIME_CONSTRAINT)) { - if (XXConstraint->StartTime.date > CurrentTime.date || - (XXConstraint->StartTime.date == CurrentTime.date && - XXConstraint->StartTime.time >= CurrentTime.time)) { - *bIsXXable = 1; - return DRM_RIGHTS_PENDING; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_END_TIME_CONSTRAINT)) { /* Have end time restrict? */ - if (XXConstraint->EndTime.date < CurrentTime.date || - (XXConstraint->EndTime.date == CurrentTime.date && - XXConstraint->EndTime.time <= CurrentTime.time)) { - XXConstraint->Indicator &= ~DRM_END_TIME_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_INTERVAL_CONSTRAINT)) { /* Have interval time restrict? */ - if (XXConstraint->Interval.date == 0 && XXConstraint->Interval.time == 0) { - XXConstraint->Indicator &= ~DRM_INTERVAL_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - *bIsXXable = 1; - return DRM_SUCCESS; -} - -int32_t drm_checkRoAndUpdate(int32_t id, int32_t permission) -{ - int32_t writeFlag = 0; - int32_t roAmount; - int32_t validRoAmount = 0; - int32_t flag = DRM_FAILURE; - int32_t i, j; - T_DRM_Rights *pRo; - T_DRM_Rights *pCurRo; - int32_t * pNumOfPriority; - int32_t iNum; - T_DRM_Rights_Constraint * pCurConstraint; - T_DRM_Rights_Constraint * pCompareConstraint; - int priority[8] = {1, 2, 4, 3, 8, 6, 7, 5}; - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return DRM_FAILURE; - - validRoAmount = roAmount; - if (roAmount < 1) - return DRM_NO_RIGHTS; - - pRo = malloc(roAmount * sizeof(T_DRM_Rights)); - pCurRo = pRo; - if (NULL == pRo) - return DRM_FAILURE; - - if (FALSE == drm_writeOrReadInfo(id, pRo, &roAmount, GET_ALL_RO)) { - free(pRo); - return DRM_FAILURE; - } - - /** check the right priority */ - pNumOfPriority = malloc(sizeof(int32_t) * roAmount); - for(i = 0; i < roAmount; i++) { - iNum = roAmount - 1; - for(j = 0; j < roAmount; j++) { - if(i == j) - continue; - switch(permission) { - case DRM_PERMISSION_PLAY: - pCurConstraint = &pRo[i].PlayConstraint; - pCompareConstraint = &pRo[j].PlayConstraint; - break; - case DRM_PERMISSION_DISPLAY: - pCurConstraint = &pRo[i].DisplayConstraint; - pCompareConstraint = &pRo[j].DisplayConstraint; - break; - case DRM_PERMISSION_EXECUTE: - pCurConstraint = &pRo[i].ExecuteConstraint; - pCompareConstraint = &pRo[j].ExecuteConstraint; - break; - case DRM_PERMISSION_PRINT: - pCurConstraint = &pRo[i].PrintConstraint; - pCompareConstraint = &pRo[j].PrintConstraint; - break; - default: - free(pRo); - free(pNumOfPriority); - return DRM_FAILURE; - } - - /**get priority by Indicator*/ - if(0 == (pCurConstraint->Indicator & DRM_NO_CONSTRAINT) && - 0 == (pCompareConstraint->Indicator & DRM_NO_CONSTRAINT)) { - int num1, num2; - num1 = (pCurConstraint->Indicator & 0x0e) >> 1; - num2 = (pCompareConstraint->Indicator & 0x0e) >> 1; - if(priority[num1] > priority[num2]) { - iNum--; - continue; - } else if(priority[pCurConstraint->Indicator] < priority[pCompareConstraint->Indicator]) - continue; - } else if(pCurConstraint->Indicator > pCompareConstraint->Indicator) { - iNum--; - continue; - } else if(pCurConstraint->Indicator < pCompareConstraint->Indicator) - continue; - - if(0 != (pCurConstraint->Indicator & DRM_END_TIME_CONSTRAINT)) { - if(pCurConstraint->EndTime.date < pCompareConstraint->EndTime.date) { - iNum--; - continue; - } else if(pCurConstraint->EndTime.date > pCompareConstraint->EndTime.date) - continue; - - if(pCurConstraint->EndTime.time < pCompareConstraint->EndTime.time) { - iNum--; - continue; - } else if(pCurConstraint->EndTime.date > pCompareConstraint->EndTime.date) - continue; - } - - if(0 != (pCurConstraint->Indicator & DRM_INTERVAL_CONSTRAINT)) { - if(pCurConstraint->Interval.date < pCompareConstraint->Interval.date) { - iNum--; - continue; - } else if(pCurConstraint->Interval.date > pCompareConstraint->Interval.date) - continue; - - if(pCurConstraint->Interval.time < pCompareConstraint->Interval.time) { - iNum--; - continue; - } else if(pCurConstraint->Interval.time > pCompareConstraint->Interval.time) - continue; - } - - if(0 != (pCurConstraint->Indicator & DRM_COUNT_CONSTRAINT)) { - if(pCurConstraint->Count < pCompareConstraint->Count) { - iNum--; - continue; - } else if(pCurConstraint->Count > pCompareConstraint->Count) - continue; - } - - if(i < j) - iNum--; - } - pNumOfPriority[iNum] = i; - } - - for (i = 0; i < validRoAmount; i++) { - /** check the right priority */ - if (pNumOfPriority[i] >= validRoAmount) - break; - - pCurRo = pRo + pNumOfPriority[i]; - - switch (permission) { - case DRM_PERMISSION_PLAY: - flag = - drm_startConsumeRights(&pCurRo->bIsPlayable, - &pCurRo->PlayConstraint, &writeFlag); - break; - case DRM_PERMISSION_DISPLAY: - flag = - drm_startConsumeRights(&pCurRo->bIsDisplayable, - &pCurRo->DisplayConstraint, - &writeFlag); - break; - case DRM_PERMISSION_EXECUTE: - flag = - drm_startConsumeRights(&pCurRo->bIsExecuteable, - &pCurRo->ExecuteConstraint, - &writeFlag); - break; - case DRM_PERMISSION_PRINT: - flag = - drm_startConsumeRights(&pCurRo->bIsPrintable, - &pCurRo->PrintConstraint, &writeFlag); - break; - default: - free(pNumOfPriority); - free(pRo); - return DRM_FAILURE; - } - - /* Here confirm the valid RO amount and set the writeFlag */ - if (0 == pCurRo->bIsPlayable && 0 == pCurRo->bIsDisplayable && - 0 == pCurRo->bIsExecuteable && 0 == pCurRo->bIsPrintable) { - int32_t iCurPri; - - /** refresh the right priority */ - iCurPri = pNumOfPriority[i]; - for(j = i; j < validRoAmount - 1; j++) - pNumOfPriority[j] = pNumOfPriority[j + 1]; - - if(iCurPri != validRoAmount - 1) { - memcpy(pCurRo, pRo + validRoAmount - 1, - sizeof(T_DRM_Rights)); - for(j = 0; j < validRoAmount -1; j++) { - if(validRoAmount - 1 == pNumOfPriority[j]) - pNumOfPriority[j] = iCurPri; - } - } - - /* Here means it is not the last one RO, so the invalid RO should be deleted */ - writeFlag = 1; - validRoAmount--; /* If current right is invalid */ - i--; - } - - /* If the flag is TRUE, this means: we have found a valid RO, so break, no need to check other RO */ - if (DRM_SUCCESS == flag) - break; - } - - if (1 == writeFlag) { - /* Delete the *.info first */ - //drm_removeIdInfoFile(id); - - if (FALSE == drm_writeOrReadInfo(id, pRo, &validRoAmount, SAVE_ALL_RO)) - flag = DRM_FAILURE; - } - - free(pNumOfPriority); - free(pRo); - return flag; -} - - -/* see svc_drm.h */ -int32_t SVC_drm_installRights(T_DRM_Input_Data data, T_DRM_Rights_Info* pRightsInfo) -{ - uint8_t *buf; - int32_t dataLen, bufLen; - T_DRM_Rights rights; - - if (0 == data.inputHandle) - return DRM_RIGHTS_DATA_INVALID; - - /* Get input rights data length */ - dataLen = data.getInputDataLength(data.inputHandle); - if (dataLen <= 0) - return DRM_RIGHTS_DATA_INVALID; - - /* Check if the length is larger than DRM max malloc length */ - if (dataLen > DRM_MAX_MALLOC_LEN) - bufLen = DRM_MAX_MALLOC_LEN; - else - bufLen = dataLen; - - buf = (uint8_t *)malloc(bufLen); - if (NULL == buf) - return DRM_FAILURE; - - /* Read input data to buffer */ - if (0 >= data.readInputData(data.inputHandle, buf, bufLen)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - - /* if the input mime type is unknown, DRM engine will try to recognize it. */ - if (TYPE_DRM_UNKNOWN == data.mimeType) - data.mimeType = getMimeType(buf, bufLen); - - switch(data.mimeType) { - case TYPE_DRM_MESSAGE: /* in case of Combined Delivery, extract the rights part to install */ - { - T_DRM_DM_Info dmInfo; - - memset(&dmInfo, 0, sizeof(T_DRM_DM_Info)); - if (FALSE == drm_parseDM(buf, bufLen, &dmInfo)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - - /* if it is not Combined Delivery, it can not use to "SVC_drm_installRights" */ - if (COMBINED_DELIVERY != dmInfo.deliveryType || dmInfo.rightsOffset <= 0 || dmInfo.rightsLen <= 0) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - - memset(&rights, 0, sizeof(T_DRM_Rights)); - if (FALSE == drm_relParser(buf + dmInfo.rightsOffset, dmInfo.rightsLen, TYPE_DRM_RIGHTS_XML, &rights)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - } - break; - case TYPE_DRM_RIGHTS_XML: - case TYPE_DRM_RIGHTS_WBXML: - memset(&rights, 0, sizeof(T_DRM_Rights)); - if (FALSE == drm_relParser(buf, bufLen, data.mimeType, &rights)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - break; - case TYPE_DRM_CONTENT: /* DCF should not using "SVC_drm_installRights", it should be used to open a session. */ - case TYPE_DRM_UNKNOWN: - default: - free(buf); - return DRM_MEDIA_DATA_INVALID; - } - - free(buf); - - /* append the rights information to DRM engine storage */ - if (FALSE == drm_appendRightsInfo(&rights)) - return DRM_FAILURE; - - memset(pRightsInfo, 0, sizeof(T_DRM_Rights_Info)); - drm_getLicenseInfo(&rights, pRightsInfo); - - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_openSession(T_DRM_Input_Data data) -{ - int32_t session; - int32_t dataLen; - T_DRM_Session_Node* s; - - if (0 == data.inputHandle) - return DRM_MEDIA_DATA_INVALID; - - /* Get input data length */ - dataLen = data.getInputDataLength(data.inputHandle); - if (dataLen <= 0) - return DRM_MEDIA_DATA_INVALID; - - s = newSession(data); - if (NULL == s) - return DRM_FAILURE; - - /* Check if the length is larger than DRM max malloc length */ - if (dataLen > DRM_MAX_MALLOC_LEN) - s->rawContentLen = DRM_MAX_MALLOC_LEN; - else - s->rawContentLen = dataLen; - - s->rawContent = (uint8_t *)malloc(s->rawContentLen); - if (NULL == s->rawContent) - return DRM_FAILURE; - - /* Read input data to buffer */ - if (0 >= data.readInputData(data.inputHandle, s->rawContent, s->rawContentLen)) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - /* if the input mime type is unknown, DRM engine will try to recognize it. */ - if (TYPE_DRM_UNKNOWN == data.mimeType) - data.mimeType = getMimeType(s->rawContent, s->rawContentLen); - - switch(data.mimeType) { - case TYPE_DRM_MESSAGE: - { - T_DRM_DM_Info dmInfo; - - memset(&dmInfo, 0, sizeof(T_DRM_DM_Info)); - if (FALSE == drm_parseDM(s->rawContent, s->rawContentLen, &dmInfo)) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - s->deliveryMethod = dmInfo.deliveryType; - - if (SEPARATE_DELIVERY_FL == s->deliveryMethod) - s->contentLength = DRM_UNKNOWN_DATA_LEN; - else - s->contentLength = dmInfo.contentLen; - - s->transferEncoding = dmInfo.transferEncoding; - s->contentOffset = dmInfo.contentOffset; - s->bEndData = FALSE; - strcpy((char *)s->contentType, (char *)dmInfo.contentType); - strcpy((char *)s->contentID, (char *)dmInfo.contentID); - - if (SEPARATE_DELIVERY_FL == s->deliveryMethod) { - s->infoStruct = (T_DRM_Dcf_Node *)malloc(sizeof(T_DRM_Dcf_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_Dcf_Node)); - - ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength = dmInfo.contentLen; - strcpy((char *)((T_DRM_Dcf_Node *)(s->infoStruct))->rightsIssuer, (char *)dmInfo.rightsIssuer); - break; - } - - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) { - s->infoStruct = (T_DRM_DM_Base64_Node *)malloc(sizeof(T_DRM_DM_Base64_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_DM_Base64_Node)); - - strcpy((char *)((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary, (char *)dmInfo.boundary); - } else { - s->infoStruct = (T_DRM_DM_Binary_Node *)malloc(sizeof(T_DRM_DM_Binary_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_DM_Binary_Node)); - - strcpy((char *)((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary, (char *)dmInfo.boundary); - } - - - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) { - if (s->contentLength > 0) { - int32_t encLen, decLen; - - encLen = s->contentLength; - decLen = encLen / DRM_B64_ENC_BLOCK * DRM_B64_DEC_BLOCK; - - decLen = drm_decodeBase64(s->rawContent, decLen, s->rawContent + s->contentOffset, &encLen); - s->contentLength = decLen; - } else { - int32_t encLen = DRM_MAX_MALLOC_LEN - s->contentOffset, decLen; - int32_t skipLen, needBytes, i; - uint8_t *pStart; - int32_t res, bFoundBoundary = FALSE; - - pStart = s->rawContent + s->contentOffset; - if (-1 == (skipLen = drm_skipCRLFinB64(pStart, encLen))) { - freeSession(s); - return DRM_FAILURE; - } - - needBytes = DRM_B64_ENC_BLOCK - ((encLen - skipLen) % DRM_B64_ENC_BLOCK); - if (needBytes < DRM_B64_ENC_BLOCK) { - s->rawContent = (uint8_t *)realloc(s->rawContent, DRM_MAX_MALLOC_LEN + needBytes); - if (NULL == s->rawContent) { - freeSession(s); - return DRM_FAILURE; - } - - i = 0; - while (i < needBytes) { - if (-1 != data.readInputData(data.inputHandle, s->rawContent + DRM_MAX_MALLOC_LEN + i, 1)) { - if ('\r' == *(s->rawContent + DRM_MAX_MALLOC_LEN + i) || '\n' == *(s->rawContent + DRM_MAX_MALLOC_LEN + i)) - continue; - i++; - } else - break; - } - encLen += i; - } - - res = drm_scanEndBoundary(pStart, encLen, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary); - if (-1 == res) { - freeSession(s); - return DRM_FAILURE; - } - if (-2 == res) { /* may be there is a boundary */ - int32_t boundaryLen, leftLen, readBytes; - char* pTmp = memrchr(pStart, '\r', encLen); - - if (NULL == pTmp) { - freeSession(s); - return DRM_FAILURE; /* conflict */ - } - boundaryLen = strlen((char *)((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary) + 2; /* 2 means: '\r''\n' */ - s->readBuf = (uint8_t *)malloc(boundaryLen); - if (NULL == s->readBuf) { - freeSession(s); - return DRM_FAILURE; - } - s->readBufOff = encLen - ((uint8_t *)pTmp - pStart); - s->readBufLen = boundaryLen - s->readBufOff; - memcpy(s->readBuf, pTmp, s->readBufOff); - readBytes = data.readInputData(data.inputHandle, s->readBuf + s->readBufOff, s->readBufLen); - if (-1 == readBytes || readBytes < s->readBufLen) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - if (0 == drm_scanEndBoundary(s->readBuf, boundaryLen, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary)) { - encLen = (uint8_t *)pTmp - pStart; /* yes, it is the end boundary */ - bFoundBoundary = TRUE; - } - } else { - if (res >= 0 && res < encLen) { - encLen = res; - bFoundBoundary = TRUE; - } - } - - decLen = encLen / DRM_B64_ENC_BLOCK * DRM_B64_DEC_BLOCK; - decLen = drm_decodeBase64(s->rawContent, decLen, s->rawContent + s->contentOffset, &encLen); - ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen = decLen; - if (bFoundBoundary) - s->contentLength = decLen; - } - } else { - /* binary data */ - if (DRM_UNKNOWN_DATA_LEN == s->contentLength) { - /* try to check whether there is boundary may be split */ - int32_t res, binContentLen; - uint8_t* pStart; - int32_t bFoundBoundary = FALSE; - - pStart = s->rawContent + s->contentOffset; - binContentLen = s->rawContentLen - s->contentOffset; - res = drm_scanEndBoundary(pStart, binContentLen, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary); - - if (-1 == res) { - freeSession(s); - return DRM_FAILURE; - } - - if (-2 == res) { /* may be the boundary is split */ - int32_t boundaryLen, leftLen, readBytes; - char* pTmp = memrchr(pStart, '\r', binContentLen); - - if (NULL == pTmp) { - freeSession(s); - return DRM_FAILURE; /* conflict */ - } - - boundaryLen = strlen((char *)((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary) + 2; /* 2 means: '\r''\n' */ - s->readBuf = (uint8_t *)malloc(boundaryLen); - if (NULL == s->readBuf) { - freeSession(s); - return DRM_FAILURE; - } - s->readBufOff = binContentLen - ((uint8_t *)pTmp - pStart); - s->readBufLen = boundaryLen - s->readBufOff; - memcpy(s->readBuf, pTmp, s->readBufOff); - readBytes = data.readInputData(data.inputHandle, s->readBuf + s->readBufOff, s->readBufLen); - if (-1 == readBytes || readBytes < s->readBufLen) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - if (0 == drm_scanEndBoundary(s->readBuf, boundaryLen, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary)) { - binContentLen = (uint8_t *)pTmp - pStart; /* yes, it is the end boundary */ - bFoundBoundary = TRUE; - } - } else { - if (res >= 0 && res < binContentLen) { - binContentLen = res; - bFoundBoundary = TRUE; - } - } - - if (bFoundBoundary) - s->contentLength = binContentLen; - } - } - } - break; - case TYPE_DRM_CONTENT: - { - T_DRM_DCF_Info dcfInfo; - uint8_t* pEncData = NULL; - - memset(&dcfInfo, 0, sizeof(T_DRM_DCF_Info)); - if (FALSE == drm_dcfParser(s->rawContent, s->rawContentLen, &dcfInfo, &pEncData)) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - s->infoStruct = (T_DRM_Dcf_Node *)malloc(sizeof(T_DRM_Dcf_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_Dcf_Node)); - - s->deliveryMethod = SEPARATE_DELIVERY; - s->contentLength = dcfInfo.DecryptedDataLen; - ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength = dcfInfo.EncryptedDataLen; - s->contentOffset = pEncData - s->rawContent; - strcpy((char *)s->contentType, (char *)dcfInfo.ContentType); - strcpy((char *)s->contentID, (char *)dcfInfo.ContentURI); - strcpy((char *)((T_DRM_Dcf_Node *)(s->infoStruct))->rightsIssuer, (char *)dcfInfo.Rights_Issuer); - } - break; - case TYPE_DRM_RIGHTS_XML: /* rights object should using "SVC_drm_installRights", it can not open a session */ - case TYPE_DRM_RIGHTS_WBXML: /* rights object should using "SVC_drm_installRights", it can not open a session */ - case TYPE_DRM_UNKNOWN: - default: - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - if ((SEPARATE_DELIVERY_FL == s->deliveryMethod || SEPARATE_DELIVERY == s->deliveryMethod) && - s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength <= DRM_MAX_MALLOC_LEN) { - uint8_t keyValue[DRM_KEY_LEN]; - uint8_t lastDcfBuf[DRM_TWO_AES_BLOCK_LEN]; - int32_t seekPos, moreBytes; - - if (TRUE == drm_getKey(s->contentID, keyValue)) { - seekPos = s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength - DRM_TWO_AES_BLOCK_LEN; - memcpy(lastDcfBuf, s->rawContent + seekPos, DRM_TWO_AES_BLOCK_LEN); - - if (TRUE == drm_updateDcfDataLen(lastDcfBuf, keyValue, &moreBytes)) { - s->contentLength = ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength; - s->contentLength -= moreBytes; - } - } - } - - session = addSession(s); - if (-1 == session) - return DRM_FAILURE; - - return session; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getDeliveryMethod(int32_t session) -{ - T_DRM_Session_Node* s; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - return s->deliveryMethod; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getContentType(int32_t session, uint8_t* mediaType) -{ - T_DRM_Session_Node* s; - - if (session < 0 || NULL == mediaType) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - strcpy((char *)mediaType, (char *)s->contentType); - - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_checkRights(int32_t session, int32_t permission) -{ - T_DRM_Session_Node* s; - int32_t id; - T_DRM_Rights *pRo, *pCurRo; - int32_t roAmount; - int32_t i; - int32_t res = DRM_FAILURE; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - /* if it is Forward-Lock cases, check it and return directly */ - if (FORWARD_LOCK == s->deliveryMethod) { - if (DRM_PERMISSION_PLAY == permission || - DRM_PERMISSION_DISPLAY == permission || - DRM_PERMISSION_EXECUTE == permission || - DRM_PERMISSION_PRINT == permission) - return DRM_SUCCESS; - - return DRM_FAILURE; - } - - /* if try to forward, only DCF can be forwarded */ - if (DRM_PERMISSION_FORWARD == permission) { - if (SEPARATE_DELIVERY == s->deliveryMethod) - return DRM_SUCCESS; - - return DRM_FAILURE; - } - - /* The following will check CD or SD other permissions */ - if (FALSE == drm_readFromUidTxt(s->contentID, &id, GET_ID)) - return DRM_FAILURE; - - drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT); - if (roAmount <= 0) - return DRM_FAILURE; - - pRo = malloc(roAmount * sizeof(T_DRM_Rights)); - if (NULL == pRo) - return DRM_FAILURE; - - drm_writeOrReadInfo(id, pRo, &roAmount, GET_ALL_RO); - - pCurRo = pRo; - for (i = 0; i < roAmount; i++) { - switch (permission) { - case DRM_PERMISSION_PLAY: - res = drm_startCheckRights(&(pCurRo->bIsPlayable), &(pCurRo->PlayConstraint)); - break; - case DRM_PERMISSION_DISPLAY: - res = drm_startCheckRights(&(pCurRo->bIsDisplayable), &(pCurRo->DisplayConstraint)); - break; - case DRM_PERMISSION_EXECUTE: - res = drm_startCheckRights(&(pCurRo->bIsExecuteable), &(pCurRo->ExecuteConstraint)); - break; - case DRM_PERMISSION_PRINT: - res = drm_startCheckRights(&(pCurRo->bIsPrintable), &(pCurRo->PrintConstraint)); - break; - default: - free(pRo); - return DRM_FAILURE; - } - - if (DRM_SUCCESS == res) { - free(pRo); - return DRM_SUCCESS; - } - pCurRo++; - } - - free(pRo); - return res; -} - -/* see svc_drm.h */ -int32_t SVC_drm_consumeRights(int32_t session, int32_t permission) -{ - T_DRM_Session_Node* s; - int32_t id; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (DRM_PERMISSION_FORWARD == permission) { - if (SEPARATE_DELIVERY == s->deliveryMethod) - return DRM_SUCCESS; - - return DRM_FAILURE; - } - - if (FORWARD_LOCK == s->deliveryMethod) /* Forwardlock type have utter rights */ - return DRM_SUCCESS; - - if (FALSE == drm_readFromUidTxt(s->contentID, &id, GET_ID)) - return DRM_FAILURE; - - return drm_checkRoAndUpdate(id, permission); -} - -/* see svc_drm.h */ -int32_t SVC_drm_getContentLength(int32_t session) -{ - T_DRM_Session_Node* s; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (DRM_UNKNOWN_DATA_LEN == s->contentLength && s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength <= DRM_MAX_MALLOC_LEN && - (SEPARATE_DELIVERY == s->deliveryMethod || SEPARATE_DELIVERY_FL == s->deliveryMethod)) { - uint8_t keyValue[DRM_KEY_LEN]; - uint8_t lastDcfBuf[DRM_TWO_AES_BLOCK_LEN]; - int32_t seekPos, moreBytes; - - if (TRUE == drm_getKey(s->contentID, keyValue)) { - seekPos = s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength - DRM_TWO_AES_BLOCK_LEN; - memcpy(lastDcfBuf, s->rawContent + seekPos, DRM_TWO_AES_BLOCK_LEN); - - if (TRUE == drm_updateDcfDataLen(lastDcfBuf, keyValue, &moreBytes)) { - s->contentLength = ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength; - s->contentLength -= moreBytes; - } - } - } - - return s->contentLength; -} - -static int32_t drm_readAesData(uint8_t* buf, T_DRM_Session_Node* s, int32_t aesStart, int32_t bufLen) -{ - if (NULL == buf || NULL == s || aesStart < 0 || bufLen < 0) - return -1; - - if (aesStart - s->contentOffset + bufLen > ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength) - return -2; - - if (aesStart < DRM_MAX_MALLOC_LEN) { - if (aesStart + bufLen <= DRM_MAX_MALLOC_LEN) { /* read from buffer */ - memcpy(buf, s->rawContent + aesStart, bufLen); - return bufLen; - } else { /* first read from buffer and then from InputStream */ - int32_t point = DRM_MAX_MALLOC_LEN - aesStart; - int32_t res; - - if (((T_DRM_Dcf_Node *)(s->infoStruct))->bAesBackupBuf) { - memcpy(buf, ((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, DRM_ONE_AES_BLOCK_LEN); - res = s->readInputDataFunc(s->inputHandle, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - if (0 == res || -1 == res) - return -1; - - res += DRM_ONE_AES_BLOCK_LEN; - } else { - memcpy(buf, s->rawContent + aesStart, point); - res = s->readInputDataFunc(s->inputHandle, buf + point, bufLen - point); - if (0 == res || -1 == res) - return -1; - - res += point; - } - - memcpy(((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - ((T_DRM_Dcf_Node *)(s->infoStruct))->bAesBackupBuf = TRUE; - - return res; - } - } else { /* read from InputStream */ - int32_t res; - - memcpy(buf, ((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, DRM_ONE_AES_BLOCK_LEN); - res = s->readInputDataFunc(s->inputHandle, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - - if (0 == res || -1 == res) - return -1; - - memcpy(((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - - return DRM_ONE_AES_BLOCK_LEN + res; - } -} - -static int32_t drm_readContentFromBuf(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t readBytes; - - if (offset > s->contentLength) - return DRM_FAILURE; - - if (offset == s->contentLength) - return DRM_MEDIA_EOF; - - if (offset + mediaBufLen > s->contentLength) - readBytes = s->contentLength - offset; - else - readBytes = mediaBufLen; - - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) - memcpy(mediaBuf, s->rawContent + offset, readBytes); - else - memcpy(mediaBuf, s->rawContent + s->contentOffset + offset, readBytes); - - return readBytes; -} - -static int32_t drm_readB64ContentFromInputStream(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - uint8_t encBuf[DRM_B64_ENC_BLOCK], decBuf[DRM_B64_DEC_BLOCK]; - int32_t encLen, decLen; - int32_t i, j, piece, leftLen, firstBytes; - int32_t readBytes = 0; - - if (offset < ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen) { - readBytes = ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen - offset; - memcpy(mediaBuf, s->rawContent + offset, readBytes); - } else { - if (s->bEndData) - return DRM_MEDIA_EOF; - - firstBytes = offset % DRM_B64_DEC_BLOCK; - if (firstBytes > 0) { - if (DRM_B64_DEC_BLOCK - firstBytes >= mediaBufLen) { - readBytes = mediaBufLen; - memcpy(mediaBuf, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeData + firstBytes, readBytes); - return readBytes; - } - - readBytes = DRM_B64_DEC_BLOCK - firstBytes; - memcpy(mediaBuf, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeData + firstBytes, readBytes); - } - } - - leftLen = mediaBufLen - readBytes; - encLen = (leftLen - 1) / DRM_B64_DEC_BLOCK * DRM_B64_ENC_BLOCK + DRM_B64_ENC_BLOCK; - piece = encLen / DRM_B64_ENC_BLOCK; - - for (i = 0; i < piece; i++) { - j = 0; - while (j < DRM_B64_ENC_BLOCK) { - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - *(encBuf + j) = s->readBuf[s->readBufOff]; - s->readBufOff++; - s->readBufLen--; - } else { /* read from InputStream */ - if (0 == s->readInputDataFunc(s->inputHandle, encBuf + j, 1)) - return DRM_MEDIA_DATA_INVALID; - } - - if ('\r' == *(encBuf + j) || '\n' == *(encBuf + j)) - continue; /* skip CRLF */ - - if ('-' == *(encBuf + j)) { - int32_t k, len; - - /* invalid base64 data, it comes to end boundary */ - if (0 != j) - return DRM_MEDIA_DATA_INVALID; - - /* check whether it is really the boundary */ - len = strlen((char *)((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary); - if (NULL == s->readBuf) { - s->readBuf = (uint8_t *)malloc(len); - if (NULL == s->readBuf) - return DRM_FAILURE; - } - - s->readBuf[0] = '-'; - for (k = 0; k < len - 1; k++) { - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - *(s->readBuf + k + 1) = s->readBuf[s->readBufOff]; - s->readBufOff++; - s->readBufLen--; - } else { /* read from InputStream */ - if (-1 == s->readInputDataFunc(s->inputHandle, s->readBuf + k + 1, 1)) - return DRM_MEDIA_DATA_INVALID; - } - } - if (0 == memcmp(s->readBuf, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary, len)) - s->bEndData = TRUE; - else - return DRM_MEDIA_DATA_INVALID; - - break; - } - j++; - } - - if (TRUE == s->bEndData) { /* it means come to the end of base64 data */ - if (0 == readBytes) - return DRM_MEDIA_EOF; - - break; - } - - encLen = DRM_B64_ENC_BLOCK; - decLen = DRM_B64_DEC_BLOCK; - if (-1 == (decLen = drm_decodeBase64(decBuf, decLen, encBuf, &encLen))) - return DRM_MEDIA_DATA_INVALID; - - if (leftLen >= decLen) { - memcpy(mediaBuf + readBytes, decBuf, decLen); - readBytes += decLen; - leftLen -= decLen; - } else { - if (leftLen > 0) { - memcpy(mediaBuf + readBytes, decBuf, leftLen); - readBytes += leftLen; - } - break; - } - } - memcpy(((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeData, decBuf, DRM_B64_DEC_BLOCK); - - return readBytes; -} - -static int32_t drm_readBase64Content(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t readBytes; - - /* when the content length has been well-known */ - if (s->contentLength >= 0) - readBytes = drm_readContentFromBuf(s, offset, mediaBuf, mediaBufLen); - else /* else when the content length has not been well-known yet */ - if (offset < ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen) - if (offset + mediaBufLen <= ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen) { - readBytes = mediaBufLen; - memcpy(mediaBuf, s->rawContent + offset, readBytes); - } else - readBytes = drm_readB64ContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - else - readBytes = drm_readB64ContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - - return readBytes; -} - -static int32_t drm_readBinaryContentFromInputStream(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t res = 0, readBytes = 0; - int32_t leftLen; - - if (s->contentOffset + offset < DRM_MAX_MALLOC_LEN) { - readBytes = DRM_MAX_MALLOC_LEN - s->contentOffset - offset; - memcpy(mediaBuf, s->rawContent + s->contentOffset + offset, readBytes); - } else - if (s->bEndData) - return DRM_MEDIA_EOF; - - leftLen = mediaBufLen - readBytes; - - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - if (leftLen <= s->readBufLen) { - memcpy(mediaBuf + readBytes, s->readBuf + s->readBufOff, leftLen); - s->readBufOff += leftLen; - s->readBufLen -= leftLen; - readBytes += leftLen; - leftLen = 0; - } else { - memcpy(mediaBuf + readBytes, s->readBuf + s->readBufOff, s->readBufLen); - s->readBufOff += s->readBufLen; - leftLen -= s->readBufLen; - readBytes += s->readBufLen; - s->readBufLen = 0; - } - } - - if (leftLen > 0) { - res = s->readInputDataFunc(s->inputHandle, mediaBuf + readBytes, mediaBufLen - readBytes); - if (-1 == res) - return DRM_MEDIA_DATA_INVALID; - } - - readBytes += res; - res = drm_scanEndBoundary(mediaBuf, readBytes, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary); - if (-1 == res) - return DRM_MEDIA_DATA_INVALID; - if (-2 == res) { /* may be the boundary is split */ - int32_t boundaryLen, len, off, k; - char* pTmp = memrchr(mediaBuf, '\r', readBytes); - - if (NULL == pTmp) - return DRM_FAILURE; /* conflict */ - - boundaryLen = strlen((char *)((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary) + 2; /* 2 means: '\r''\n' */ - if (NULL == s->readBuf) { - s->readBuf = (uint8_t *)malloc(boundaryLen); - if (NULL == s->readBuf) - return DRM_FAILURE; - } - - off = readBytes - ((uint8_t *)pTmp - mediaBuf); - len = boundaryLen - off; - memcpy(s->readBuf, pTmp, off); - for (k = 0; k < boundaryLen - off; k++) { - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - *(s->readBuf + k + off) = s->readBuf[s->readBufOff]; - s->readBufOff++; - s->readBufLen--; - } else { /* read from InputStream */ - if (-1 == s->readInputDataFunc(s->inputHandle, s->readBuf + k + off, 1)) - return DRM_MEDIA_DATA_INVALID; - } - } - s->readBufOff = off; - s->readBufLen = len; - - if (0 == drm_scanEndBoundary(s->readBuf, boundaryLen, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary)) { - readBytes = (uint8_t *)pTmp - mediaBuf; /* yes, it is the end boundary */ - s->bEndData = TRUE; - } - } else { - if (res >= 0 && res < readBytes) { - readBytes = res; - s->bEndData = TRUE; - } - } - - if (s->bEndData) { - if (0 == readBytes) - return DRM_MEDIA_EOF; - } - - return readBytes; -} - -static int32_t drm_readBinaryContent(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t readBytes; - - if (s->contentLength >= 0) - readBytes = drm_readContentFromBuf(s, offset, mediaBuf, mediaBufLen); - else /* else when the content length has not been well-known yet */ - if (s->contentOffset + offset < DRM_MAX_MALLOC_LEN) - if (s->contentOffset + offset + mediaBufLen <= DRM_MAX_MALLOC_LEN) { - readBytes = mediaBufLen; - memcpy(mediaBuf, s->rawContent + s->contentOffset + offset, readBytes); - } else - readBytes = drm_readBinaryContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - else - readBytes = drm_readBinaryContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - - return readBytes; -} - -static int32_t drm_readAesContent(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - uint8_t keyValue[DRM_KEY_LEN]; - uint8_t buf[DRM_TWO_AES_BLOCK_LEN]; - int32_t readBytes = 0; - int32_t bufLen, piece, i, copyBytes, leftBytes; - int32_t aesStart, mediaStart, mediaBufOff; - AES_KEY key; - - if (FALSE == drm_getKey(s->contentID, keyValue)) - return DRM_NO_RIGHTS; - - /* when the content length has been well-known */ - if (s->contentLength > 0) { - if (offset > s->contentLength) - return DRM_FAILURE; - - if (offset == s->contentLength) - return DRM_MEDIA_EOF; - - if (offset + mediaBufLen > s->contentLength) - readBytes = s->contentLength - offset; - else - readBytes = mediaBufLen; - - aesStart = s->contentOffset + (offset / DRM_ONE_AES_BLOCK_LEN * DRM_ONE_AES_BLOCK_LEN); - piece = (offset + readBytes - 1) / DRM_ONE_AES_BLOCK_LEN - offset / DRM_ONE_AES_BLOCK_LEN + 2; - mediaStart = offset % DRM_ONE_AES_BLOCK_LEN; - - AES_set_decrypt_key(keyValue, DRM_KEY_LEN * 8, &key); - mediaBufOff = 0; - leftBytes = readBytes; - - for (i = 0; i < piece - 1; i++) { - memcpy(buf, s->rawContent + aesStart + i * DRM_ONE_AES_BLOCK_LEN, DRM_TWO_AES_BLOCK_LEN); - bufLen = DRM_TWO_AES_BLOCK_LEN; - - if (drm_aesDecBuffer(buf, &bufLen, &key) < 0) - return DRM_MEDIA_DATA_INVALID; - - if (0 != i) - mediaStart = 0; - - if (bufLen - mediaStart <= leftBytes) - copyBytes = bufLen - mediaStart; - else - copyBytes = leftBytes; - - memcpy(mediaBuf + mediaBufOff, buf + mediaStart, copyBytes); - leftBytes -= copyBytes; - mediaBufOff += copyBytes; - } - } else { - int32_t res; - - if (s->bEndData) - return DRM_MEDIA_EOF; - - if (((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen > ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff) { - if (mediaBufLen < ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff) - copyBytes = mediaBufLen; - else - copyBytes = ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff; - - memcpy(mediaBuf, ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecData + ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff, copyBytes); - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff += copyBytes; - readBytes += copyBytes; - } - - leftBytes = mediaBufLen - readBytes; - if (0 == leftBytes) - return readBytes; - if (leftBytes < 0) - return DRM_FAILURE; - - offset += readBytes; - aesStart = s->contentOffset + (offset / DRM_ONE_AES_BLOCK_LEN * DRM_ONE_AES_BLOCK_LEN); - piece = (offset + leftBytes - 1) / DRM_ONE_AES_BLOCK_LEN - offset / DRM_ONE_AES_BLOCK_LEN + 2; - mediaBufOff = readBytes; - - AES_set_decrypt_key(keyValue, DRM_KEY_LEN * 8, &key); - - for (i = 0; i < piece - 1; i++) { - if (-1 == (res = drm_readAesData(buf, s, aesStart, DRM_TWO_AES_BLOCK_LEN))) - return DRM_MEDIA_DATA_INVALID; - - if (-2 == res) - break; - - bufLen = DRM_TWO_AES_BLOCK_LEN; - aesStart += DRM_ONE_AES_BLOCK_LEN; - - if (drm_aesDecBuffer(buf, &bufLen, &key) < 0) - return DRM_MEDIA_DATA_INVALID; - - drm_discardPaddingByte(buf, &bufLen); - - if (bufLen <= leftBytes) - copyBytes = bufLen; - else - copyBytes = leftBytes; - - memcpy(mediaBuf + mediaBufOff, buf, copyBytes); - leftBytes -= copyBytes; - mediaBufOff += copyBytes; - readBytes += copyBytes; - } - - memcpy(((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecData, buf, DRM_ONE_AES_BLOCK_LEN); - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen = bufLen; - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff = copyBytes; - - if (aesStart - s->contentOffset > ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength - DRM_TWO_AES_BLOCK_LEN && ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff == ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen) { - s->bEndData = TRUE; - if (0 == readBytes) - return DRM_MEDIA_EOF; - } - } - - return readBytes; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getContent(int32_t session, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - T_DRM_Session_Node* s; - int32_t readBytes; - - if (session < 0 || offset < 0 || NULL == mediaBuf || mediaBufLen <= 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (0 >= s->getInputDataLengthFunc(s->inputHandle)) - return DRM_MEDIA_DATA_INVALID; - - switch(s->deliveryMethod) { - case FORWARD_LOCK: - case COMBINED_DELIVERY: - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) - readBytes = drm_readBase64Content(s, offset, mediaBuf, mediaBufLen); - else /* binary */ - readBytes = drm_readBinaryContent(s, offset, mediaBuf, mediaBufLen); - break; - case SEPARATE_DELIVERY: - case SEPARATE_DELIVERY_FL: - readBytes = drm_readAesContent(s, offset, mediaBuf, mediaBufLen); - break; - default: - return DRM_FAILURE; - } - - return readBytes; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getRightsIssuer(int32_t session, uint8_t* rightsIssuer) -{ - T_DRM_Session_Node* s; - - if (session < 0 || NULL == rightsIssuer) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (SEPARATE_DELIVERY == s->deliveryMethod || SEPARATE_DELIVERY_FL == s->deliveryMethod) { - strcpy((char *)rightsIssuer, (char *)((T_DRM_Dcf_Node *)(s->infoStruct))->rightsIssuer); - return DRM_SUCCESS; - } - - return DRM_NOT_SD_METHOD; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getRightsInfo(int32_t session, T_DRM_Rights_Info* rights) -{ - T_DRM_Session_Node* s; - T_DRM_Rights rightsInfo; - int32_t roAmount, id; - - if (session < 0 || NULL == rights) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (FORWARD_LOCK == s->deliveryMethod) { - strcpy((char *)rights->roId, "ForwardLock"); - rights->displayRights.indicator = DRM_NO_CONSTRAINT; - rights->playRights.indicator = DRM_NO_CONSTRAINT; - rights->executeRights.indicator = DRM_NO_CONSTRAINT; - rights->printRights.indicator = DRM_NO_CONSTRAINT; - return DRM_SUCCESS; - } - - if (FALSE == drm_readFromUidTxt(s->contentID, &id, GET_ID)) - return DRM_NO_RIGHTS; - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return DRM_FAILURE; - - if (roAmount < 0) - return DRM_NO_RIGHTS; - - /* some rights has been installed, but now there is no valid rights */ - if (0 == roAmount) { - strcpy((char *)rights->roId, s->contentID); - rights->displayRights.indicator = DRM_NO_PERMISSION; - rights->playRights.indicator = DRM_NO_PERMISSION; - rights->executeRights.indicator = DRM_NO_PERMISSION; - rights->printRights.indicator = DRM_NO_PERMISSION; - return DRM_SUCCESS; - } - - roAmount = 1; - memset(&rightsInfo, 0, sizeof(T_DRM_Rights)); - if (FALSE == drm_writeOrReadInfo(id, &rightsInfo, &roAmount, GET_A_RO)) - return DRM_FAILURE; - - memset(rights, 0, sizeof(T_DRM_Rights_Info)); - drm_getLicenseInfo(&rightsInfo, rights); - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_closeSession(int32_t session) -{ - if (session < 0) - return DRM_FAILURE; - - if (NULL == getSession(session)) - return DRM_SESSION_NOT_OPENED; - - removeSession(session); - - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_updateRights(uint8_t* contentID, int32_t permission) -{ - int32_t id; - - if (NULL == contentID) - return DRM_FAILURE; - - if (FALSE == drm_readFromUidTxt(contentID, &id, GET_ID)) - return DRM_FAILURE; - - return drm_checkRoAndUpdate(id, permission); -} - -/* see svc_drm.h */ -int32_t SVC_drm_viewAllRights(T_DRM_Rights_Info_Node **ppRightsInfo) -{ - T_DRM_Rights_Info_Node rightsNode; - int32_t maxId, id, roAmount, j; - T_DRM_Rights rights; - - memset(&rights, 0, sizeof(T_DRM_Rights)); - - if (NULL == ppRightsInfo) - return DRM_FAILURE; - - *ppRightsInfo = NULL; - - maxId = drm_getMaxIdFromUidTxt(); - if (-1 == maxId) - return DRM_FAILURE; - - for (id = 1; id <= maxId; id++) { - drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT); - if (roAmount <= 0) /* this means there is not any rights */ - continue; - - for (j = 1; j <= roAmount; j++) { - if (FALSE == drm_writeOrReadInfo(id, &rights, &j, GET_A_RO)) - continue; - - memset(&rightsNode, 0, sizeof(T_DRM_Rights_Info_Node)); - - drm_getLicenseInfo(&rights, &(rightsNode.roInfo)); - - if (FALSE == drm_addRightsNodeToList(ppRightsInfo, &rightsNode)) - continue; - } - } - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_freeRightsInfoList(T_DRM_Rights_Info_Node *pRightsHeader) -{ - T_DRM_Rights_Info_Node *pNode, *pTmp; - - if (NULL == pRightsHeader) - return DRM_FAILURE; - - pNode = pRightsHeader; - - while (NULL != pNode) { - pTmp = pNode; - pNode = pNode->next; - free(pTmp); - } - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_deleteRights(uint8_t* roId) -{ - int32_t maxId, id, roAmount, j; - T_DRM_Rights rights; - - memset(&rights, 0, sizeof(T_DRM_Rights)); - - if (NULL == roId) - return DRM_FAILURE; - - maxId = drm_getMaxIdFromUidTxt(); - if (-1 == maxId) - return DRM_NO_RIGHTS; - - for (id = 1; id <= maxId; id++) { - drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT); - if (roAmount <= 0) /* this means there is not any rights */ - continue; - - for (j = 1; j <= roAmount; j++) { - if (FALSE == drm_writeOrReadInfo(id, &rights, &j, GET_A_RO)) - continue; - - /* here find the RO which will be deleted */ - if (0 == strcmp((char *)rights.uid, (char *)roId)) { - T_DRM_Rights *pAllRights; - - pAllRights = (T_DRM_Rights *)malloc(roAmount * sizeof(T_DRM_Rights)); - if (NULL == pAllRights) - return DRM_FAILURE; - - drm_writeOrReadInfo(id, pAllRights, &roAmount, GET_ALL_RO); - roAmount--; - if (0 == roAmount) { /* this means it is the last one rights */ - drm_removeIdInfoFile(id); /* delete the id.info file first */ - drm_updateUidTxtWhenDelete(id); /* update uid.txt file */ - free(pAllRights); - return DRM_SUCCESS; - } else /* using the last one rights instead of the deleted one */ - memcpy(pAllRights + (j - 1), pAllRights + roAmount, sizeof(T_DRM_Rights)); - - /* delete the id.info file first */ -// drm_removeIdInfoFile(id); - - if (FALSE == drm_writeOrReadInfo(id, pAllRights, &roAmount, SAVE_ALL_RO)) { - free(pAllRights); - return DRM_FAILURE; - } - - free(pAllRights); - return DRM_SUCCESS; - } - } - } - - return DRM_FAILURE; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_decoder.c b/media/libdrm/mobile1/src/objmng/drm_decoder.c deleted file mode 100644 index 82c7efb7e0d02f87567373ef86e008ca4f7040ce..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/objmng/drm_decoder.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include - -/* global variables */ -static const uint8_t * base64_alphabet = (const uint8_t *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -#define SKIP_CRLF(p) while('\r' == *(p) || '\n' == *(p)) \ - p++ - -static int8_t get_alphabet_index(int8_t ch) -{ - uint8_t * tmp; - - if ('=' == ch) - return 64; - - tmp = (uint8_t *)strchr((const char *)base64_alphabet, ch); - if (NULL == tmp) - return -1; - - return (int8_t)(tmp - base64_alphabet); -} - -/* See drm_decoder.h */ -int32_t drm_decodeBase64(uint8_t * dest, int32_t destLen, uint8_t * src, int32_t * srcLen) -{ - int32_t maxDestSize, i, maxGroup; - uint8_t *pDest, *pSrc; - int8_t tpChar; - - if (NULL == src || NULL == srcLen || *srcLen <= 0 || destLen < 0) - return -1; - - maxDestSize = (*srcLen) * 3/4; - if (NULL == dest || 0 == destLen) - return maxDestSize; - - if (destLen < maxDestSize) - maxDestSize = destLen; - maxGroup = maxDestSize/3; - - pDest = dest; /* start to decode src to dest */ - pSrc = src; - for (i = 0; i < maxGroup && *srcLen - (pSrc - src) >= 4; i++) { - SKIP_CRLF(pSrc); - if (pSrc - src >= *srcLen) - break; - tpChar = get_alphabet_index(*pSrc); /* to first byte */ - if (-1 == tpChar || 64 == tpChar) - return -1; - pDest[0] = tpChar << 2; - pSrc++; - SKIP_CRLF(pSrc); - tpChar = get_alphabet_index(*pSrc); - if (-1 == tpChar || 64 == tpChar) - return -1; - pDest[0] |= (tpChar >> 4); - pDest[1] = tpChar << 4; /* to second byte */ - pSrc++; - SKIP_CRLF(pSrc); - tpChar = get_alphabet_index(*pSrc); - if (-1 == tpChar) - return -1; - if (64 == tpChar) /* end */ - return pDest - dest + 1; - pDest[1] |= (tpChar >> 2); - pDest[2] = tpChar << 6; /* to third byte */ - pSrc++; - SKIP_CRLF(pSrc); - tpChar = get_alphabet_index(*pSrc); - if (-1 == tpChar) - return -1; - if (64 == tpChar) /* end */ - return pDest - dest + 2; - pDest[2] |= tpChar; - pDest += 3; - pSrc++; - } - *srcLen = pSrc - src; - return pDest - dest; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_file.c b/media/libdrm/mobile1/src/objmng/drm_file.c deleted file mode 100644 index e6c303e083454ae5b974433d4413f68f36900414..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/objmng/drm_file.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * Fails on zaurus? - #define DEVICE_FILESYSTEM -*/ -#define DEFAULT_TOTAL_SPACE (4L * 1024L * 1024L) /* 4 Meg. */ - -#ifndef DEVICE_FILESYSTEM -/* Store the total space on FS VM can use. */ -static int32_t totalSpace; -/* how many remain space can VM use. */ -static int32_t availableSize; -#endif - -extern char* getStorageRoot(void); - -static char tmpPathBuf1[MAX_FILENAME_LEN]; -static char tmpPathBuf2[MAX_FILENAME_LEN]; - -static int32_t -convertFilename(const uint16_t *strData, int32_t strLength, char *buffer); - -static int calcDirSize(char *path, int len, uint8_t includeSubdirs); - -#ifndef DEVICE_FILESYSTEM -static void initFsVariables(void); -#endif - -/** - * Convert a Java string into a nul terminated ascii string to pass to posix - * @param strData first character of name - * @param strLength number of characters in name - * @param buffer Buffer to store terminated string in (at least MAXPATHLEN) - * @return Length of filename in characters (excl. nul), or -1 on failure. - */ -static int32_t -convertFilename(const uint16_t *strData, int32_t strLength, char *buffer) -{ - int idx; - - if (strLength >= (MAXPATHLEN-1)) - { - Trace("convertFilename '%.*S' too long", strLength, strData); - return -1; - } - - for (idx = 0; idx < strLength; ++idx) - *buffer++ = (char)*strData++; - - *buffer = 0; - return strLength; -} - - -/** - * Perform a stat() call on the given filename. - * Helper for getFileLength and exists - * @param name unicode name - * @param nameLen number of unicode characters in name - * @param sbuf stat buffer - * @return TRUE on success, FALSE on failure - */ -static int32_t -getFileStat(const uint16_t *name, int32_t nameLen, struct stat *sbuf) -{ - Trace("getFileStat: %.*S", nameLen, name); - - if (convertFilename(name, nameLen, tmpPathBuf1) <= 0) - { - Trace("getFileStat: bad filename"); - } - else if (stat(tmpPathBuf1, sbuf) != 0) - { - Trace("getFileStat %s: stat() errno=%d", tmpPathBuf1, errno); - } - else /* Successful */ - { - return TRUE; - } - - return FALSE; -} - -#ifndef DEVICE_FILESYSTEM -/** - * initial the variables like totalSpace, availableSize... - */ -static void initFsVariables(void) -{ - totalSpace = DEFAULT_TOTAL_SPACE; - - availableSize = totalSpace; -} -#endif /* DEVICE_FILESYSTEM */ - -/** - * calculate the size of everything inside path pointed directory - * this function will use path pointed buffer to store some extra info - * so param len is needed. - * @param path the directory path need to calculate - * @param len length of the path buffer, not the path string length - * @param includeSubdirs also calculate all the subdirs in path holds? - * @return the calculated size, DRM_FILE_FAILURE on failure. - */ -static int calcDirSize(char *path, int len, uint8_t includeSubdirs) -{ - struct dirent *ent; - struct stat stat_buf; - - DIR *dir = NULL; - int size = 0; - int exists = -1; - int dirPathLen = strlen(path); - - /* Ensure space for wildcard */ - if((dirPathLen + 2) >= MAXPATHLEN || (dirPathLen + 2) >= len) - { - return DRM_FILE_FAILURE; - } - - if(path[dirPathLen - 1] != '/') - { - path[dirPathLen++] = '/'; - path[dirPathLen] = '\0'; - } - - dir = opendir(path); - if (dir == NULL) - { - return DRM_FILE_FAILURE; - } - - while ((ent = readdir(dir)) != NULL ) - { - if (strcmp(ent->d_name, ".") == 0 || - strcmp(ent->d_name, "..") == 0) - { - continue; - } - - path[dirPathLen] = '\0'; - if ((int)(strlen(ent->d_name) + dirPathLen + 1) < len) - { - strcat(path, ent->d_name); - } - else - { - continue; - } - - exists = stat(path, &stat_buf); - if (exists != -1) - { - /* exclude the storage occupied by directory itself */ - if (stat_buf.st_mode & S_IFDIR) - { - if(includeSubdirs) - { - /* calculate the size recursively */ - int ret; - ret = calcDirSize(path, len, includeSubdirs); - /* ignore failure in subdirs */ - if( DRM_FILE_FAILURE != ret ) - { - size += ret; - } - } - } - else - { - size += stat_buf.st_size; - } - } - } - - closedir(dir); - return size; -} - -/* see drm_file.h */ -int32_t DRM_file_startup(void) -{ - Trace("DRM_file_startup"); - -#ifndef DEVICE_FILESYSTEM - availableSize = -1; - - initFsVariables(); -#endif - - return DRM_FILE_SUCCESS; /* Nothing to do */ -} - -/* see drm_file.h */ -int32_t -DRM_file_listOpen(const uint16_t *prefix, - int32_t prefixLen, - int32_t* session, - int32_t* iteration) -{ - Trace("DRM_file_listOpen: %.*S", prefixLen, prefix); - - if (convertFilename(prefix, prefixLen, tmpPathBuf1) <= 0) - { - Trace("DRM_file_listOpen: bad filename"); - } - else - { - DIR *dir; - - /* find the last /, and store the offset to the leaf prefix in - * *iteration - */ - - char *sep = strrchr(tmpPathBuf1, '/'); - /* Root "/" is a leaf */ - if (sep == NULL || ((sep != NULL) && (sep == tmpPathBuf1))) - { - *iteration = prefixLen; - -#ifdef TRACE_ON - sep = " "; /* trace will show sep+1 */ -#endif - } - else - { - *iteration = sep - tmpPathBuf1 + 1; - *sep = 0; - } - - dir = opendir(tmpPathBuf1); - - if (dir == NULL) - { - Trace("DRM_file_listOpen: opendir %s: errno=%d", tmpPathBuf1, errno); - } - else - { - Trace("DRM_file_listOpen: dir %s, filter %s", tmpPathBuf1, sep+1); - *session = (int32_t)dir; - return DRM_FILE_SUCCESS; - } - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_listNextEntry(const uint16_t *prefix, int32_t prefixLen, - uint16_t* entry, int32_t entrySize, - int32_t *session, int32_t* iteration) -{ - struct dirent *ent; - - /* We stored the offset of the leaf part of the prefix (if any) - * in *iteration - */ - const uint16_t* strData = prefix + *iteration; - int32_t strLength = prefixLen - *iteration; - - /* entrySize is bytes for some reason. Convert to ucs chars */ - entrySize /= 2; - - /* Now we want to filter for files which start with the (possibly empty) - * sequence at strData. We have to return fully-qualified filenames, - * which means *iteration characters from prefix, plus the - * leaf name. - */ - - while ( (ent = readdir((DIR *)*session)) != NULL) - { - int len = strlen(ent->d_name); - - if ( (len + *iteration) > entrySize) - { - Trace("DRM_file_listNextEntry: %s too long", ent->d_name); - } - else if (strcmp(ent->d_name, ".") != 0 && - strcmp(ent->d_name, "..") != 0) - { - int idx; - struct stat sinfo; - - /* check against the filter */ - - for (idx = 0; idx < strLength; ++idx) - { - if (ent->d_name[idx] != strData[idx]) - goto next_name; - } - - Trace("DRM_file_listNextEntry: matched %s", ent->d_name); - - /* Now generate the fully-qualified name */ - - for (idx = 0; idx < *iteration; ++idx) - entry[idx] = prefix[idx]; - - for (idx = 0; idx < len; ++idx) - entry[*iteration + idx] = (unsigned char)ent->d_name[idx]; - - /*add "/" at the end of a DIR file entry*/ - if (getFileStat(entry, idx + *iteration, &sinfo)){ - if (S_ISDIR(sinfo.st_mode) && - (idx + 1 + *iteration) < entrySize) { - entry[*iteration + idx] = '/'; - ++idx; - } - } - else - { - Trace("DRM_file_listNextEntry: stat FAILURE on %.*S", - idx + *iteration, entry); - } - Trace("DRM_file_listNextEntry: got %.*S", idx + *iteration, entry); - - return idx + *iteration; - } - - next_name: - Trace("DRM_file_listNextEntry: rejected %s", ent->d_name); - } - - Trace("DRM_file_listNextEntry: end of list"); - return 0; -} - -/* see drm_file.h */ -int32_t -DRM_file_listClose(int32_t session, int32_t iteration) -{ - closedir( (DIR *)session); - return DRM_FILE_SUCCESS; -} - -/* see drm_file.h */ -int32_t -DRM_file_getFileLength(const uint16_t *name, int32_t nameLen) -{ - struct stat sbuf; - - if (getFileStat(name, nameLen, &sbuf)) - { - if (sbuf.st_size >= INT32_MAX) - { - Trace("DRM_file_getFileLength: file too big"); - } - else /* Successful */ - { - Trace("DRM_file_getFileLength: %.*S -> %d", - nameLen, name, (int32_t)sbuf.st_size); - return (int32_t)sbuf.st_size; - } - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_delete(const uint16_t *name, int32_t nameLen) -{ - Trace("DRM_file_delete: %.*S", nameLen, name); - - if (convertFilename(name, nameLen, tmpPathBuf1) <= 0) - { - Trace("DRM_file_delete: bad filename"); - return DRM_FILE_FAILURE; - } - else - { - struct stat sinfo; - if (stat(tmpPathBuf1, &sinfo) != 0){ - Trace("DRM_file_delete: stat failed, errno=%d", errno); - return DRM_FILE_FAILURE; - } -#ifndef DEVICE_FILESYSTEM - if (S_ISDIR(sinfo.st_mode)){ - /* it's a dir */ - if (rmdir(tmpPathBuf1) != 0){ - Trace("DRM_file_delete: dir remove failed, errno=%d", errno); - return DRM_FILE_FAILURE; - } - else - { - return DRM_FILE_SUCCESS; - } - } -#endif - /* it's a file */ - if (unlink(tmpPathBuf1) != 0) - { - Trace("DRM_file_delete: file remove failed, errno=%d", errno); - return DRM_FILE_FAILURE; - } - else - { -#ifndef DEVICE_FILESYSTEM - availableSize += sinfo.st_size; -#endif - return DRM_FILE_SUCCESS; - } - } - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_rename(const uint16_t *oldName, int32_t oldNameLen, - const uint16_t *newName, int32_t newNameLen) -{ - Trace("DRM_file_rename %.*S -> %.*S", - oldNameLen, oldName, newNameLen, newName); - if (DRM_file_exists(newName, newNameLen) != DRM_FILE_FAILURE) - { - Trace("DRM_file_rename: filename:%s exist",newName); - return DRM_FILE_FAILURE; - } - - if (convertFilename(oldName, oldNameLen, tmpPathBuf1) <= 0 || - convertFilename(newName, newNameLen, tmpPathBuf2) <= 0) - { - Trace("DRM_file_rename: bad filename"); - } - else if (rename(tmpPathBuf1, tmpPathBuf2) != 0) - { - Trace("DRM_file_rename: failed errno=%d", errno); - } - else /* Success */ - { - return DRM_FILE_SUCCESS; - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_exists(const uint16_t *name, int32_t nameLen) -{ - struct stat sbuf; - - Trace("DRM_file_exists: %.*S", nameLen, name); - - /*remove trailing "/" separators, except the first "/" standing for root*/ - while ((nameLen > 1) && (name[nameLen -1] == '/')) - --nameLen; - - if (getFileStat(name, nameLen, &sbuf)) - { - Trace("DRM_file_exists: stat returns mode 0x%x", sbuf.st_mode); - - if (S_ISDIR(sbuf.st_mode)) - return DRM_FILE_ISDIR; - if (S_ISREG(sbuf.st_mode)) - return DRM_FILE_ISREG; - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_open(const uint16_t *name, int32_t nameLen, int32_t mode, - int32_t* handle) -{ - int res; - -#if DRM_FILE_MODE_READ != 1 || DRM_FILE_MODE_WRITE != 2 -#error constants changed -#endif - - /* Convert DRM file modes to posix modes */ - static const int modes[4] = - { 0, - O_RDONLY, - O_WRONLY | O_CREAT, - O_RDWR | O_CREAT - }; - - Trace("DRM_file_open %.*S mode 0x%x", nameLen, name, mode); - - assert((mode & ~(DRM_FILE_MODE_READ|DRM_FILE_MODE_WRITE)) == 0); - - if (convertFilename(name, nameLen, tmpPathBuf1) <= 0) - { - Trace("DRM_file_open: bad filename"); - return DRM_FILE_FAILURE; - } - - if ((res = open(tmpPathBuf1, modes[mode], 0777)) == -1) - { - Trace("DRM_file_open: open failed errno=%d", errno); - return DRM_FILE_FAILURE; - } - - Trace("DRM_file_open: open '%s; returned %d", tmpPathBuf1, res); - *handle = res; - - return DRM_FILE_SUCCESS; -} - -/* see drm_file.h */ -int32_t -DRM_file_read(int32_t handle, uint8_t* dst, int32_t length) -{ - int n; - - assert(length > 0); - - /* TODO: Make dst a void *? */ - - n = read((int)handle, dst, (size_t)length); - if (n > 0) - { - Trace("DRM_file_read handle=%d read %d bytes", handle, n); - return n; - } - else if (n == 0) - { - Trace("DRM_file_read read EOF: handle=%d", handle); - return DRM_FILE_EOF; - } - else - { - Trace("DRM_file_read failed handle=%d, errno=%d", handle, errno); - return DRM_FILE_FAILURE; - } -} - -/* see drm_file.h */ -int32_t -DRM_file_write(int32_t handle, const uint8_t* src, int32_t length) -{ - /* TODO: Make dst a void *? */ - int n; -#ifndef DEVICE_FILESYSTEM - int delta; - off_t prevPos; - struct stat sbuf; - int prevFileSize; -#endif - - assert(length >= 0); - -#ifndef DEVICE_FILESYSTEM - if ( -1 == fstat((int)handle, &sbuf) ) - { - Trace("DRM_file_write: fstat error %d", errno); - return DRM_FILE_FAILURE; - } - prevFileSize = (int)(sbuf.st_size); - prevPos = lseek( (int)handle, 0, SEEK_CUR); - if ( (off_t)-1 == prevPos ) - { - Trace("DRM_file_write: get current pos error %d", errno); - return DRM_FILE_FAILURE; - } - delta = (int)prevPos + length - prevFileSize; - if (delta > availableSize) - { - Trace("DRM_file_write: not enough size!"); - return DRM_FILE_FAILURE; - } -#endif - n = write((int)handle, src, (size_t)length); - if (n < 0) - { - Trace("DRM_file_write failed errno=%d", errno); - return DRM_FILE_FAILURE; - } -#ifndef DEVICE_FILESYSTEM - delta = prevPos + n - prevFileSize; - - if ( delta > 0 ) - { - availableSize -= delta; - } -#endif - Trace("DRM_file_write handle=%d wrote %d/%d bytes", handle, n, length); - - return n; -} - -/* see drm_file.h */ -int32_t DRM_file_close(int32_t handle) -{ - if (close((int)handle) == 0) - { - Trace("DRM_file_close handle=%d success", handle); - return DRM_FILE_SUCCESS; - } - - Trace("DRM_file_close handle=%d failed", handle); - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_setPosition(int32_t handle, int32_t value) -{ -#ifndef DEVICE_FILESYSTEM - struct stat sbuf; -#endif - off_t newPos; - - if (value < 0) - { - Trace("DRM_file_setPosition: handle=%d negative value (%d)", - handle, value); - return DRM_FILE_FAILURE; - } - -#ifndef DEVICE_FILESYSTEM - if ( fstat((int)handle, &sbuf) == -1 ) - { - Trace("DRM_file_setPosition: fstat fail errno=%d", errno); - return DRM_FILE_FAILURE; - } - - if ( ((off_t)value > sbuf.st_size) && - (availableSize < (value - (int)(sbuf.st_size))) ) - { - Trace("DRM_file_setPosition: not enough space"); - return DRM_FILE_FAILURE; - } -#endif - - newPos = lseek( (int)handle, (off_t)value, SEEK_SET); - if ( newPos == (off_t)-1 ) - { - Trace("DRM_file_setPosition: seek failed: errno=%d", errno); - } - else - { -#ifndef DEVICE_FILESYSTEM - if ( newPos > sbuf.st_size ) - { - availableSize -= (int)(newPos - sbuf.st_size); - } -#endif - return DRM_FILE_SUCCESS; - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_mkdir(const uint16_t* name, int32_t nameChars) -{ - Trace("DRM_file_mkdir started!.."); - - if (convertFilename(name, nameChars, tmpPathBuf1) <= 0) - { - Trace("DRM_file_mkdir: bad filename"); - return DRM_FILE_FAILURE; - } - - if (mkdir(tmpPathBuf1,0777) != 0) - { - Trace("DRM_file_mkdir failed!errno=%d",errno); - return DRM_FILE_FAILURE; - } - - return DRM_FILE_SUCCESS; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_i18n.c b/media/libdrm/mobile1/src/objmng/drm_i18n.c deleted file mode 100644 index b1118a9843baef20dee1b53f05f6dc5e182b9676..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/objmng/drm_i18n.c +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include - -#define IS_GB2312_HIGH_BYTE(c) ((c) >= 0xA1 && (c) <= 0xF7) -#define IS_GB2312_LOW_BYTE(c) ((c) >= 0xA1 && (c) <= 0xFE) -#define IS_GBK_HIGH_BYTE(c) ((c) >= 0x81 && (c) <= 0xFE) -#define IS_GBK_LOW_BYTE(c) ((c) >= 0x40 && (c) <= 0xFE && (c) != 0x7F) -#define IS_BIG5_HIGH_BYTE(c) ((c) >= 0xA1 && (c) <= 0xF9) -#define IS_BIG5_LOW_BYTE(c) (((c) >= 0x40 && (c) <= 0x7E) \ - || ((c) >= 0xA1 && (c) <= 0xFE)) -#define IS_ASCII(c) ((c) <= 127) - -#define INVALID_UNICODE 0xFFFD - -#define I18N_LATIN1_SUPPORT -#define I18N_UTF8_UTF16_SUPPORT - - -/** - * Simply convert ISO 8859-1 (latin1) to unicode - */ -static int32_t latin1ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to ISO 8859-1 (latin1) byte - */ -static int32_t wcToLatin1(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/** - * Convert UTF-8 to unicode - */ -static int32_t utf8ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to UTF-8 bytes - */ -static int32_t wcToUtf8(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/** - * Convert UTF-16 BE to unicode - */ -static int32_t utf16beToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to UTF-16 BE bytes - */ -static int32_t wcToUtf16be(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/** - * Convert UTF-16 LE to unicode - */ -static int32_t utf16leToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to UTF-16 LE bytes - */ -static int32_t wcToUtf16le(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/* - * see drm_i18n.h - */ -int32_t DRM_i18n_mbsToWcs(DRM_Charset_t charset, - const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - switch (charset) - { -#ifdef I18N_GB2312_SUPPORT - case DRM_CHARSET_GB2312: - return gb2312ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_GBK_SUPPORT - case DRM_CHARSET_GBK: - return gbkToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_BIG5_SUPPORT - case DRM_CHARSET_BIG5: - return big5ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_LATIN1_SUPPORT - case DRM_CHARSET_LATIN1: - return latin1ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_ISO8859X_SUPPORT - case DRM_CHARSET_LATIN2: - case DRM_CHARSET_LATIN3: - case DRM_CHARSET_LATIN4: - case DRM_CHARSET_CYRILLIC: - case DRM_CHARSET_ARABIC: - case DRM_CHARSET_GREEK: - case DRM_CHARSET_HEBREW: - case DRM_CHARSET_LATIN5: - case DRM_CHARSET_LATIN6: - case DRM_CHARSET_THAI: - case DRM_CHARSET_LATIN7: - case DRM_CHARSET_LATIN8: - case DRM_CHARSET_LATIN9: - case DRM_CHARSET_LATIN10: - return iso8859xToWcs(charset, mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_UTF8_UTF16_SUPPORT - case DRM_CHARSET_UTF8: - return utf8ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); - case DRM_CHARSET_UTF16BE: - return utf16beToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); - case DRM_CHARSET_UTF16LE: - return utf16leToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif - default: - return -1; - } -} - -/* - * see drm_i18n.h - */ -int32_t DRM_i18n_wcsToMbs(DRM_Charset_t charset, - const uint16_t *wcs, int32_t wcsLen, - uint8_t *mbsBuf, int32_t bufSizeInByte) -{ - int32_t (* wcToMbFunc)(uint16_t, uint8_t *, int32_t); - int32_t charIndex = 0; - int32_t numMultiBytes = 0; - - switch (charset) - { -#ifdef I18N_LATIN1_SUPPORT - case DRM_CHARSET_LATIN1: - wcToMbFunc = wcToLatin1; - break; -#endif -#ifdef I18N_UTF8_UTF16_SUPPORT - case DRM_CHARSET_UTF8: - wcToMbFunc = wcToUtf8; - break; - case DRM_CHARSET_UTF16BE: - wcToMbFunc = wcToUtf16be; - break; - case DRM_CHARSET_UTF16LE: - wcToMbFunc = wcToUtf16le; - break; -#endif -#ifdef I18N_ISO8859X_SUPPORT - case DRM_CHARSET_LATIN2: - case DRM_CHARSET_LATIN3: - case DRM_CHARSET_LATIN4: - case DRM_CHARSET_CYRILLIC: - case DRM_CHARSET_ARABIC: - case DRM_CHARSET_GREEK: - case DRM_CHARSET_HEBREW: - case DRM_CHARSET_LATIN5: - case DRM_CHARSET_LATIN6: - case DRM_CHARSET_THAI: - case DRM_CHARSET_LATIN7: - case DRM_CHARSET_LATIN8: - case DRM_CHARSET_LATIN9: - case DRM_CHARSET_LATIN10: - return wcsToIso8859x(charset, wcs, wcsLen, mbsBuf, bufSizeInByte); -#endif - default: - return -1; - } - - if (mbsBuf) { - while (numMultiBytes < bufSizeInByte && charIndex < wcsLen) { - /* TODO: handle surrogate pair values here */ - int32_t mbLen = wcToMbFunc(wcs[charIndex], - &mbsBuf[numMultiBytes], bufSizeInByte - numMultiBytes); - - if (numMultiBytes + mbLen > bufSizeInByte) { - /* Insufficient buffer. Don't update numMultiBytes */ - break; - } - charIndex++; - numMultiBytes += mbLen; - } - } else { - while (charIndex < wcsLen) { - /* TODO: handle surrogate pair values here */ - numMultiBytes += wcToMbFunc(wcs[charIndex], NULL, 0); - charIndex++; - } - } - - return numMultiBytes; -} - - -#ifdef I18N_LATIN1_SUPPORT - -int32_t latin1ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsToConvert; - int32_t len; - - if (wcsBuf == NULL) { - return mbsLen; - } - - len = charsToConvert = mbsLen > bufSizeInWideChar ? bufSizeInWideChar : mbsLen; - if (len < 0) - return 0; - while (len--) { - *wcsBuf++ = *mbs++; - } - - if (bytesConsumed) - *bytesConsumed = charsToConvert; - - return charsToConvert; -} - -int32_t wcToLatin1(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - uint8_t ch; - - if (wc < 0x100) { - ch = (uint8_t)(wc & 0xff); - } else { - ch = '?'; - } - if (mbs && bufSize > 0) - *mbs = ch; - return 1; -} - -#endif /* I18N_LATIN1_SUPPORT */ - -#ifdef I18N_UTF8_UTF16_SUPPORT - -int32_t utf8ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsConverted = 0; - int32_t i = 0; - int32_t wideChar; - - if (wcsBuf == NULL) { - /* No conversion but we're still going to calculate bytesConsumed */ - bufSizeInWideChar = mbsLen * 2; - } - - while((i < mbsLen) && (charsConverted < bufSizeInWideChar)) { - uint8_t ch = mbs[i]; - uint8_t ch2, ch3, ch4; - - wideChar = -1; - - if(IS_ASCII(ch)) { - wideChar = ch; - i++; - } else if ((ch & 0xc0) == 0xc0) { - int utfStart = i; - if ((ch & 0xe0) == 0xc0) { - /* 2 byte sequence */ - if (i + 1 < mbsLen && ((ch2 = mbs[i + 1]) & 0xc0) == 0x80) { - wideChar = (uint16_t)(((ch & 0x1F) << 6) | (ch2 & 0x3F)); - i += 2; - } else { - /* skip incomplete sequence */ - i++; - } - } else if ((ch & 0xf0) == 0xe0) { - /* 3 byte sequence */ - if (i + 2 < mbsLen - && ((ch2 = mbs[i + 1]) & 0xc0) == 0x80 - && ((ch3 = mbs[i + 2]) & 0xc0) == 0x80) { - wideChar = (uint16_t)(((ch & 0x0F) << 12) | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F)); - i += 3; - } else { - /* skip incomplete sequence (up to 2 bytes) */ - i++; - if (i < mbsLen && (mbs[i] & 0xc0) == 0x80) - i++; - } - } else if ((ch & 0xf8) == 0xf0) { - /* 4 byte sequence */ - if (i + 3 < mbsLen - && ((ch2 = mbs[i + 1]) & 0xc0) == 0x80 - && ((ch3 = mbs[i + 2]) & 0xc0) == 0x80 - && ((ch4 = mbs[i + 3]) & 0xc0) == 0x80) { - /* FIXME: we do NOT support U+10000 - U+10FFFF for now. - * leave it as 0xFFFD. */ - wideChar = INVALID_UNICODE; - i += 4; - } else { - /* skip incomplete sequence (up to 3 bytes) */ - i++; - if (i < mbsLen && (mbs[i] & 0xc0) == 0x80) { - i++; - if (i < mbsLen && (mbs[i] & 0xc0) == 0x80) { - i++; - } - } - } - } else { - /* invalid */ - i++; - } - if (i >= mbsLen && wideChar == -1) { - /* Possible incomplete UTF-8 sequence at the end of mbs. - * Leave it to the caller. - */ - i = utfStart; - break; - } - } else { - /* invalid */ - i++; - } - if(wcsBuf) { - if (wideChar == -1) - wideChar = INVALID_UNICODE; - wcsBuf[charsConverted] = (uint16_t)wideChar; - } - charsConverted++; - } - - if (bytesConsumed) - *bytesConsumed = i; - - return charsConverted; -} - -int32_t wcToUtf8(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - if (wc <= 0x7f) { - if (mbs && (bufSize >= 1)) { - *mbs = (uint8_t)wc; - } - return 1; - } else if (wc <= 0x7ff) { - if (mbs && (bufSize >= 2)) { - *mbs++ = (uint8_t)((wc >> 6) | 0xc0); - *mbs = (uint8_t)((wc & 0x3f) | 0x80); - } - return 2; - } else { - if (mbs && (bufSize >= 3)) { - *mbs++ = (uint8_t)((wc >> 12) | 0xe0); - *mbs++ = (uint8_t)(((wc >> 6) & 0x3f)| 0x80); - *mbs = (uint8_t)((wc & 0x3f) | 0x80); - } - return 3; - } -} - -int32_t utf16beToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsToConvert; - int32_t len; - - if (wcsBuf == NULL) { - return mbsLen / 2; - } - - len = charsToConvert = (mbsLen / 2) > bufSizeInWideChar ? bufSizeInWideChar : (mbsLen / 2); - while (len--) { - /* TODO: handle surrogate pair values */ - *wcsBuf++ = (uint16_t)((*mbs << 8) | *(mbs + 1)); - mbs += 2; - } - - if (bytesConsumed) - *bytesConsumed = charsToConvert * 2; - - return charsToConvert; -} - -int32_t wcToUtf16be(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - if (mbs && bufSize >= 2) { - /* TODO: handle surrogate pair values */ - *mbs = (uint8_t)(wc >> 8); - *(mbs + 1) = (uint8_t)(wc & 0xff); - } - return 2; -} - -int32_t utf16leToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsToConvert; - int32_t len; - - if (wcsBuf == NULL) { - return mbsLen / 2; - } - - len = charsToConvert = (mbsLen / 2) > bufSizeInWideChar ? bufSizeInWideChar : (mbsLen / 2); - while (len--) { - /* TODO: handle surrogate pair values */ - *wcsBuf++ = (uint16_t)(*mbs | (*(mbs + 1) << 8)); - mbs += 2; - } - - if (bytesConsumed) - *bytesConsumed = charsToConvert * 2; - - return charsToConvert; -} - -int32_t wcToUtf16le(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - if (mbs && bufSize >= 2) { - /* TODO: handle surrogate pair values */ - *mbs = (uint8_t)(wc & 0xff); - *(mbs + 1) = (uint8_t)(wc >> 8); - } - return 2; -} - -#endif /* I18N_UTF8_UTF16_SUPPORT */ - diff --git a/media/libdrm/mobile1/src/objmng/drm_rights_manager.c b/media/libdrm/mobile1/src/objmng/drm_rights_manager.c deleted file mode 100644 index df22327b1d24231205b060868f464f33db2ec745..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/objmng/drm_rights_manager.c +++ /dev/null @@ -1,688 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include -#include -#include -#include - -static int32_t drm_getString(uint8_t* string, int32_t len, int32_t handle) -{ - int32_t i; - - for (i = 0; i < len; i++) { - if (DRM_FILE_FAILURE == DRM_file_read(handle, &string[i], 1)) - return FALSE; - if (string[i] == '\n') { - string[i + 1] = '\0'; - break; - } - } - return TRUE; -} - -static int32_t drm_putString(uint8_t* string, int32_t handle) -{ - int32_t i = 0; - - for (i = 0;; i++) { - if (string[i] == '\0') - break; - if (DRM_FILE_FAILURE == DRM_file_write(handle, &string[i], 1)) - return FALSE; - } - return TRUE; -} - -static int32_t drm_writeToUidTxt(uint8_t* Uid, int32_t* id) -{ - int32_t length; - int32_t i; - uint8_t idStr[8]; - int32_t idMax; - uint8_t(*uidStr)[256]; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - if (*id < 1) - return FALSE; - - /* convert in ucs2 */ - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - DRM_file_write(handle, (uint8_t *)"0\n", 2); - DRM_file_close(handle); - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - } - - if (!drm_getString(idStr, 8, handle)) { - DRM_file_close(handle); - return FALSE; - } - idMax = atoi((char *)idStr); - - if (idMax < *id) - uidStr = malloc((idMax + 1) * 256); - else - uidStr = malloc(idMax * 256); - - for (i = 0; i < idMax; i++) { - if (!drm_getString(uidStr[i], 256, handle)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - } - length = strlen((char *)Uid); - strcpy((char *)uidStr[*id - 1], (char *)Uid); - uidStr[*id - 1][length] = '\n'; - uidStr[*id - 1][length + 1] = '\0'; - if (idMax < (*id)) - idMax++; - DRM_file_close(handle); - - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - sprintf((char *)idStr, "%d", idMax); - - if (!drm_putString(idStr, handle)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t *)"\n", 1)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - for (i = 0; i < idMax; i++) { - if (!drm_putString(uidStr[i], handle)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - } - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t *)"\n", 1)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - DRM_file_close(handle); - free(uidStr); - return TRUE; -} - -/* See objmng_files.h */ -int32_t drm_readFromUidTxt(uint8_t* Uid, int32_t* id, int32_t option) -{ - int32_t i; - uint8_t p[256] = { 0 }; - uint8_t idStr[8]; - int32_t idMax = 0; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - if (NULL == id || NULL == Uid) - return FALSE; - - DRM_file_startup(); - - /* convert in ucs2 */ - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - DRM_file_write(handle, (uint8_t *)"0\n", 2); - DRM_file_close(handle); - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - } - - if (!drm_getString(idStr, 8, handle)) { - DRM_file_close(handle); - return FALSE; - } - idMax = atoi((char *)idStr); - - if (option == GET_UID) { - if (*id < 1 || *id > idMax) { - DRM_file_close(handle); - return FALSE; - } - for (i = 1; i <= *id; i++) { - if (!drm_getString(Uid, 256, handle)) { - DRM_file_close(handle); - return FALSE; - } - } - DRM_file_close(handle); - return TRUE; - } - if (option == GET_ID) { - *id = -1; - for (i = 1; i <= idMax; i++) { - if (!drm_getString(p, 256, handle)) { - DRM_file_close(handle); - return FALSE; - } - if (strstr((char *)p, (char *)Uid) != NULL - && strlen((char *)p) == strlen((char *)Uid) + 1) { - *id = i; - DRM_file_close(handle); - return TRUE; - } - if ((*id == -1) && (strlen((char *)p) < 3)) - *id = i; - } - if (*id != -1) { - DRM_file_close(handle); - return FALSE; - } - *id = idMax + 1; - DRM_file_close(handle); - return FALSE; - } - DRM_file_close(handle); - return FALSE; -} - -static int32_t drm_acquireId(uint8_t* uid, int32_t* id) -{ - if (TRUE == drm_readFromUidTxt(uid, id, GET_ID)) - return TRUE; - - drm_writeToUidTxt(uid, id); - - return FALSE; /* The Uid is not exit, then return FALSE indicate it */ -} - -int32_t drm_writeOrReadInfo(int32_t id, T_DRM_Rights* Ro, int32_t* RoAmount, int32_t option) -{ - uint8_t fullname[MAX_FILENAME_LEN] = {0}; - int32_t tmpRoAmount; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - sprintf((char *)fullname, ANDROID_DRM_CORE_PATH"%d"EXTENSION_NAME_INFO, id); - - /* convert in ucs2 */ - nameLen = strlen((char *)fullname); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - fullname, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - if (GET_ALL_RO == option || GET_A_RO == option) - return FALSE; - - if (GET_ROAMOUNT == option) { - *RoAmount = -1; - return TRUE; - } - } - - DRM_file_close(handle); - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ | DRM_FILE_MODE_WRITE, - &handle); - - switch(option) { - case GET_ROAMOUNT: - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)RoAmount, sizeof(int32_t))) { - DRM_file_close(handle); - return FALSE; - } - break; - case GET_ALL_RO: - DRM_file_setPosition(handle, sizeof(int32_t)); - - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)Ro, (*RoAmount) * sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - break; - case SAVE_ALL_RO: - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*)RoAmount, sizeof(int32_t))) { - DRM_file_close(handle); - return FALSE; - } - - if (NULL != Ro && *RoAmount >= 1) { - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*) Ro, (*RoAmount) * sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - } - break; - case GET_A_RO: - DRM_file_setPosition(handle, sizeof(int32_t) + (*RoAmount - 1) * sizeof(T_DRM_Rights)); - - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)Ro, sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - break; - case SAVE_A_RO: - DRM_file_setPosition(handle, sizeof(int32_t) + (*RoAmount - 1) * sizeof(T_DRM_Rights)); - - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*)Ro, sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - - DRM_file_setPosition(handle, 0); - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)&tmpRoAmount, sizeof(int32_t))) { - DRM_file_close(handle); - return FALSE; - } - if (tmpRoAmount < *RoAmount) { - DRM_file_setPosition(handle, 0); - DRM_file_write(handle, (uint8_t*)RoAmount, sizeof(int32_t)); - } - break; - default: - DRM_file_close(handle); - return FALSE; - } - - DRM_file_close(handle); - return TRUE; -} - -int32_t drm_appendRightsInfo(T_DRM_Rights* rights) -{ - int32_t id; - int32_t roAmount; - - if (NULL == rights) - return FALSE; - - drm_acquireId(rights->uid, &id); - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return FALSE; - - if (-1 == roAmount) - roAmount = 0; - - /* The RO amount increase */ - roAmount++; - - /* Save the rights information */ - if (FALSE == drm_writeOrReadInfo(id, rights, &roAmount, SAVE_A_RO)) - return FALSE; - - return TRUE; -} - -int32_t drm_getMaxIdFromUidTxt() -{ - uint8_t idStr[8]; - int32_t idMax = 0; - uint16_t nameUcs2[MAX_FILENAME_LEN] = {0}; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - /* convert in ucs2 */ - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - - /* this means the uid.txt file is not exist, so there is not any DRM object */ - if (DRM_FILE_SUCCESS != fileRes) - return 0; - - if (!drm_getString(idStr, 8, handle)) { - DRM_file_close(handle); - return -1; - } - DRM_file_close(handle); - - idMax = atoi((char *)idStr); - return idMax; -} - -int32_t drm_removeIdInfoFile(int32_t id) -{ - uint8_t filename[MAX_FILENAME_LEN] = {0}; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - - if (id <= 0) - return FALSE; - - sprintf((char *)filename, ANDROID_DRM_CORE_PATH"%d"EXTENSION_NAME_INFO, id); - - /* convert in ucs2 */ - nameLen = strlen((char *)filename); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - filename, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - if (DRM_FILE_SUCCESS != DRM_file_delete(nameUcs2, nameLen)) - return FALSE; - - return TRUE; -} - -int32_t drm_updateUidTxtWhenDelete(int32_t id) -{ - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - int32_t bufferLen; - uint8_t *buffer; - uint8_t idStr[8]; - int32_t idMax; - - if (id <= 0) - return FALSE; - - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - bufferLen = DRM_file_getFileLength(nameUcs2, nameLen); - if (bufferLen <= 0) - return FALSE; - - buffer = (uint8_t *)malloc(bufferLen); - if (NULL == buffer) - return FALSE; - - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - free(buffer); - return FALSE; - } - - drm_getString(idStr, 8, handle); - idMax = atoi((char *)idStr); - - bufferLen -= strlen((char *)idStr); - fileRes = DRM_file_read(handle, buffer, bufferLen); - buffer[bufferLen] = '\0'; - DRM_file_close(handle); - - /* handle this buffer */ - { - uint8_t *pStart, *pEnd; - int32_t i, movLen; - - pStart = buffer; - pEnd = pStart; - for (i = 0; i < id; i++) { - if (pEnd != pStart) - pStart = ++pEnd; - while ('\n' != *pEnd) - pEnd++; - if (pStart == pEnd) - pStart--; - } - movLen = bufferLen - (pEnd - buffer); - memmove(pStart, pEnd, movLen); - bufferLen -= (pEnd - pStart); - } - - if (DRM_FILE_SUCCESS != DRM_file_delete(nameUcs2, nameLen)) { - free(buffer); - return FALSE; - } - - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - free(buffer); - return FALSE; - } - sprintf((char *)idStr, "%d", idMax); - drm_putString(idStr, handle); - DRM_file_write(handle, (uint8_t*)"\n", 1); - DRM_file_write(handle, buffer, bufferLen); - free(buffer); - DRM_file_close(handle); - return TRUE; -} - -int32_t drm_getKey(uint8_t* uid, uint8_t* KeyValue) -{ - T_DRM_Rights ro; - int32_t id, roAmount; - - if (NULL == uid || NULL == KeyValue) - return FALSE; - - if (FALSE == drm_readFromUidTxt(uid, &id, GET_ID)) - return FALSE; - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return FALSE; - - if (roAmount <= 0) - return FALSE; - - memset(&ro, 0, sizeof(T_DRM_Rights)); - roAmount = 1; - if (FALSE == drm_writeOrReadInfo(id, &ro, &roAmount, GET_A_RO)) - return FALSE; - - memcpy(KeyValue, ro.KeyValue, DRM_KEY_LEN); - return TRUE; -} - -void drm_discardPaddingByte(uint8_t *decryptedBuf, int32_t *decryptedBufLen) -{ - int32_t tmpLen = *decryptedBufLen; - int32_t i; - - if (NULL == decryptedBuf || *decryptedBufLen < 0) - return; - - /* Check whether the last several bytes are padding or not */ - for (i = 1; i < decryptedBuf[tmpLen - 1]; i++) { - if (decryptedBuf[tmpLen - 1 - i] != decryptedBuf[tmpLen - 1]) - break; /* Not the padding bytes */ - } - if (i == decryptedBuf[tmpLen - 1]) /* They are padding bytes */ - *decryptedBufLen = tmpLen - i; - return; -} - -int32_t drm_aesDecBuffer(uint8_t * Buffer, int32_t * BufferLen, AES_KEY *key) -{ - uint8_t dbuf[3 * DRM_ONE_AES_BLOCK_LEN], buf[DRM_ONE_AES_BLOCK_LEN]; - uint64_t i, len, wlen = DRM_ONE_AES_BLOCK_LEN, curLen, restLen; - uint8_t *pTarget, *pTargetHead; - - pTargetHead = Buffer; - pTarget = Buffer; - curLen = 0; - restLen = *BufferLen; - - if (restLen > 2 * DRM_ONE_AES_BLOCK_LEN) { - len = 2 * DRM_ONE_AES_BLOCK_LEN; - } else { - len = restLen; - } - memcpy(dbuf, Buffer, (size_t)len); - restLen -= len; - Buffer += len; - - if (len < 2 * DRM_ONE_AES_BLOCK_LEN) { /* The original file is less than one block in length */ - len -= DRM_ONE_AES_BLOCK_LEN; - /* Decrypt from position len to position len + DRM_ONE_AES_BLOCK_LEN */ - AES_decrypt((dbuf + len), (dbuf + len), key); - - /* Undo the CBC chaining */ - for (i = 0; i < len; ++i) - dbuf[i] ^= dbuf[i + DRM_ONE_AES_BLOCK_LEN]; - - /* Output the decrypted bytes */ - memcpy(pTarget, dbuf, (size_t)len); - pTarget += len; - } else { - uint8_t *b1 = dbuf, *b2 = b1 + DRM_ONE_AES_BLOCK_LEN, *b3 = b2 + DRM_ONE_AES_BLOCK_LEN, *bt; - - for (;;) { /* While some ciphertext remains, prepare to decrypt block b2 */ - /* Read in the next block to see if ciphertext stealing is needed */ - b3 = Buffer; - if (restLen > DRM_ONE_AES_BLOCK_LEN) { - len = DRM_ONE_AES_BLOCK_LEN; - } else { - len = restLen; - } - restLen -= len; - Buffer += len; - - /* Decrypt the b2 block */ - AES_decrypt((uint8_t *)b2, buf, key); - - if (len == 0 || len == DRM_ONE_AES_BLOCK_LEN) { /* No ciphertext stealing */ - /* Unchain CBC using the previous ciphertext block in b1 */ - for (i = 0; i < DRM_ONE_AES_BLOCK_LEN; ++i) - buf[i] ^= b1[i]; - } else { /* Partial last block - use ciphertext stealing */ - wlen = len; - /* Produce last 'len' bytes of plaintext by xoring with */ - /* The lowest 'len' bytes of next block b3 - C[N-1] */ - for (i = 0; i < len; ++i) - buf[i] ^= b3[i]; - - /* Reconstruct the C[N-1] block in b3 by adding in the */ - /* Last (DRM_ONE_AES_BLOCK_LEN - len) bytes of C[N-2] in b2 */ - for (i = len; i < DRM_ONE_AES_BLOCK_LEN; ++i) - b3[i] = buf[i]; - - /* Decrypt the C[N-1] block in b3 */ - AES_decrypt((uint8_t *)b3, (uint8_t *)b3, key); - - /* Produce the last but one plaintext block by xoring with */ - /* The last but two ciphertext block */ - for (i = 0; i < DRM_ONE_AES_BLOCK_LEN; ++i) - b3[i] ^= b1[i]; - - /* Write decrypted plaintext blocks */ - memcpy(pTarget, b3, DRM_ONE_AES_BLOCK_LEN); - pTarget += DRM_ONE_AES_BLOCK_LEN; - } - - /* Write the decrypted plaintext block */ - memcpy(pTarget, buf, (size_t)wlen); - pTarget += wlen; - - if (len != DRM_ONE_AES_BLOCK_LEN) { - *BufferLen = pTarget - pTargetHead; - return 0; - } - - /* Advance the buffer pointers */ - bt = b1, b1 = b2, b2 = b3, b3 = bt; - } - } - return 0; -} - -int32_t drm_updateDcfDataLen(uint8_t* pDcfLastData, uint8_t* keyValue, int32_t* moreBytes) -{ - AES_KEY key; - int32_t len = DRM_TWO_AES_BLOCK_LEN; - - if (NULL == pDcfLastData || NULL == keyValue) - return FALSE; - - AES_set_decrypt_key(keyValue, DRM_KEY_LEN * 8, &key); - - if (drm_aesDecBuffer(pDcfLastData, &len, &key) < 0) - return FALSE; - - drm_discardPaddingByte(pDcfLastData, &len); - - *moreBytes = DRM_TWO_AES_BLOCK_LEN - len; - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_time.c b/media/libdrm/mobile1/src/objmng/drm_time.c deleted file mode 100644 index fceb4952283f9f567397c24cb483fdb5dbb46138..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/objmng/drm_time.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -/** - * @file - * DRM 1.0 Reference Port: linux implementation of drm_time.c. - */ - -#include -#include - -/* See drm_time.h */ -uint32_t DRM_time_getElapsedSecondsFrom1970(void) -{ - return time(NULL); -} - -/* See drm_time.h */ -void DRM_time_sleep(uint32_t ms) -{ - usleep(ms * 1000); -} - -/* See drm_time.h */ -void DRM_time_getSysTime(T_DB_TIME_SysTime *time_ptr) -{ - time_t t; - struct tm *tm_t; - - time(&t); - tm_t = gmtime(&t); - - time_ptr->year = tm_t->tm_year + 1900; - time_ptr->month = tm_t->tm_mon + 1; - time_ptr->day = tm_t->tm_mday; - time_ptr->hour = tm_t->tm_hour; - time_ptr->min = tm_t->tm_min; - time_ptr->sec = tm_t->tm_sec; -} diff --git a/media/libdrm/mobile1/src/parser/parser_dcf.c b/media/libdrm/mobile1/src/parser/parser_dcf.c deleted file mode 100644 index 3eac1203a90d6a64f69de462075f9c0d58bed7ba..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/parser/parser_dcf.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include -#include - -static int32_t drm_parseUintVar(uint8_t * buffer, uint8_t * len) -{ - int32_t i; - int32_t byteLen; - int32_t sum; - - if (NULL == buffer) - return DRM_UINT_VAR_ERR; - - byteLen = 0; - while ((buffer[byteLen] & UINT_VAR_FLAG) > 0 && byteLen < MAX_UINT_VAR_BYTE) /* UINT_VAR_FLAG == 0x80 */ - byteLen++; - - if (byteLen >= MAX_UINT_VAR_BYTE) /* MAX_UINT_VAR_BYTE == 5 */ - return DRM_UINT_VAR_ERR; /* The var int is too large, and that is impossible */ - - *len = (uint8_t)(byteLen + 1); - sum = buffer[byteLen]; - for (i = byteLen - 1; i >= 0; i--) - sum += ((buffer[i] & UINT_VAR_DATA) << 7 * (byteLen - i)); /* UINT_VAR_DATA == 0x7F */ - - return sum; -} - -/* See parser_dcf.h */ -int32_t drm_dcfParser(uint8_t *buffer, int32_t bufferLen, T_DRM_DCF_Info *pDcfInfo, - uint8_t **ppEncryptedData) -{ - uint8_t *tmpBuf; - uint8_t *pStart, *pEnd; - uint8_t *pHeader, *pData; - uint8_t varLen; - - if (NULL == buffer || bufferLen <= 0 || NULL == pDcfInfo) - return FALSE; - - tmpBuf = buffer; - /* 1. Parse the version, content-type and content-url */ - pDcfInfo->Version = *(tmpBuf++); - if (0x01 != pDcfInfo->Version) /* Because it is OMA DRM v1.0, the vension must be 1 */ - return FALSE; - - pDcfInfo->ContentTypeLen = *(tmpBuf++); - if (pDcfInfo->ContentTypeLen >= MAX_CONTENT_TYPE_LEN) - return FALSE; - - pDcfInfo->ContentURILen = *(tmpBuf++); - if (pDcfInfo->ContentURILen >= MAX_CONTENT_URI_LEN) - return FALSE; - - strncpy((char *)pDcfInfo->ContentType, (char *)tmpBuf, pDcfInfo->ContentTypeLen); - pDcfInfo->ContentType[MAX_CONTENT_TYPE_LEN - 1] = 0; - tmpBuf += pDcfInfo->ContentTypeLen; - strncpy((char *)pDcfInfo->ContentURI, (char *)tmpBuf, pDcfInfo->ContentURILen); - pDcfInfo->ContentURI[MAX_CONTENT_URI_LEN - 1] = 0; - tmpBuf += pDcfInfo->ContentURILen; - - /* 2. Get the headers length and data length */ - pDcfInfo->HeadersLen = drm_parseUintVar(tmpBuf, &varLen); - if (DRM_UINT_VAR_ERR == pDcfInfo->HeadersLen) - return FALSE; - tmpBuf += varLen; - pDcfInfo->DecryptedDataLen = DRM_UNKNOWN_DATA_LEN; - pDcfInfo->EncryptedDataLen = drm_parseUintVar(tmpBuf, &varLen); - if (DRM_UINT_VAR_ERR == pDcfInfo->EncryptedDataLen) - return FALSE; - tmpBuf += varLen; - pHeader = tmpBuf; - tmpBuf += pDcfInfo->HeadersLen; - pData = tmpBuf; - - /* 3. Parse the headers */ - pStart = pHeader; - while (pStart < pData) { - pEnd = pStart; - while ('\r' != *pEnd && pEnd < pData) - pEnd++; - - if (0 == strncmp((char *)pStart, HEADER_ENCRYPTION_METHOD, HEADER_ENCRYPTION_METHOD_LEN)) { - if ((pEnd - pStart - HEADER_ENCRYPTION_METHOD_LEN) >= MAX_ENCRYPTION_METHOD_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Encryption_Method, - (char *)(pStart + HEADER_ENCRYPTION_METHOD_LEN), - pEnd - pStart - HEADER_ENCRYPTION_METHOD_LEN); - pDcfInfo->Encryption_Method[MAX_ENCRYPTION_METHOD_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_RIGHTS_ISSUER, HEADER_RIGHTS_ISSUER_LEN)) { - if ((pEnd - pStart - HEADER_RIGHTS_ISSUER_LEN) >= MAX_RIGHTS_ISSUER_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Rights_Issuer, - (char *)(pStart + HEADER_RIGHTS_ISSUER_LEN), - pEnd - pStart - HEADER_RIGHTS_ISSUER_LEN); - pDcfInfo->Rights_Issuer[MAX_RIGHTS_ISSUER_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_CONTENT_NAME, HEADER_CONTENT_NAME_LEN)) { - if ((pEnd - pStart - HEADER_CONTENT_NAME_LEN) >= MAX_CONTENT_NAME_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Content_Name, - (char *)(pStart + HEADER_CONTENT_NAME_LEN), - pEnd - pStart - HEADER_CONTENT_NAME_LEN); - pDcfInfo->Content_Name[MAX_CONTENT_NAME_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_CONTENT_DESCRIPTION, HEADER_CONTENT_DESCRIPTION_LEN)) { - if ((pEnd - pStart - HEADER_CONTENT_DESCRIPTION_LEN) >= MAX_CONTENT_DESCRIPTION_LEN) - return FALSE; - strncpy((char *)pDcfInfo->ContentDescription, - (char *)(pStart + HEADER_CONTENT_DESCRIPTION_LEN), - pEnd - pStart - HEADER_CONTENT_DESCRIPTION_LEN); - pDcfInfo->ContentDescription[MAX_CONTENT_DESCRIPTION_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_CONTENT_VENDOR, HEADER_CONTENT_VENDOR_LEN)) { - if ((pEnd - pStart - HEADER_CONTENT_VENDOR_LEN) >= MAX_CONTENT_VENDOR_LEN) - return FALSE; - strncpy((char *)pDcfInfo->ContentVendor, - (char *)(pStart + HEADER_CONTENT_VENDOR_LEN), - pEnd - pStart - HEADER_CONTENT_VENDOR_LEN); - pDcfInfo->ContentVendor[MAX_CONTENT_VENDOR_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_ICON_URI, HEADER_ICON_URI_LEN)) { - if ((pEnd - pStart - HEADER_ICON_URI_LEN) >= MAX_ICON_URI_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Icon_URI, - (char *)(pStart + HEADER_ICON_URI_LEN), - pEnd - pStart - HEADER_ICON_URI_LEN); - pDcfInfo->Icon_URI[MAX_ICON_URI_LEN - 1] = 0; - } - - if ('\n' == *(pEnd + 1)) - pStart = pEnd + 2; /* Two bytes: a '\r' and a '\n' */ - else - pStart = pEnd + 1; - } - - /* 4. Give out the location of encrypted data */ - if (NULL != ppEncryptedData) - *ppEncryptedData = pData; - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/parser/parser_dm.c b/media/libdrm/mobile1/src/parser/parser_dm.c deleted file mode 100644 index 4b4a2dadf87a7cc14c2a294a98bd3a924ad28446..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/parser/parser_dm.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include -#include -#include -#include "log.h" - -#define DRM_SKIP_SPACE_TAB(p) while( (*(p) == ' ') || (*(p) == '\t') ) \ - p++ - -typedef enum _DM_PARSE_STATUS { - DM_PARSE_START, - DM_PARSING_RIGHTS, - DM_PARSING_CONTENT, - DM_PARSE_END -} DM_PARSE_STATUS; - -static int drm_strnicmp(const uint8_t* s1, const uint8_t* s2, int32_t n) -{ - if (n < 0 || NULL == s1 || NULL == s2) - return -1; - - if (n == 0) - return 0; - - while (n-- != 0 && tolower(*s1) == tolower(*s2)) - { - if (n == 0 || *s1 == '\0' || *s2 == '\0') - break; - s1++; - s2++; - } - - return tolower(*s1) - tolower(*s2); -} - -const uint8_t * drm_strnstr(const uint8_t * str, const uint8_t * strSearch, int32_t len) -{ - int32_t i, stringLen; - - if (NULL == str || NULL == strSearch || len <= 0) - return NULL; - - stringLen = strlen((char *)strSearch); - for (i = 0; i < len - stringLen + 1; i++) { - if (str[i] == *strSearch && 0 == memcmp(str + i, strSearch, stringLen)) - return str + i; - } - return NULL; -} - -/* See parser_dm.h */ -int32_t drm_parseDM(const uint8_t *buffer, int32_t bufferLen, T_DRM_DM_Info *pDmInfo) -{ - const uint8_t *pStart = NULL, *pEnd = NULL; - const uint8_t *pBufferEnd; - int32_t contentLen, leftLen; - DM_PARSE_STATUS status = DM_PARSE_START; - int32_t boundaryLen; - - if (NULL == buffer || bufferLen <= 0 || NULL == pDmInfo) - return FALSE; - - /* Find the end of the input buffer */ - pBufferEnd = buffer + bufferLen; - leftLen = bufferLen; - - /* Find out the boundary */ - pStart = drm_strnstr(buffer, (uint8_t *)"--", bufferLen); - if (NULL == pStart) - return FALSE; /* No boundary error */ - pEnd = pStart; - - /* Record the boundary */ - pEnd = drm_strnstr(pStart, (uint8_t *)DRM_NEW_LINE_CRLF, leftLen); - /* if can not find the CRLF, return FALSE */ - if (NULL == pEnd) - return FALSE; - if ((pEnd - pStart) >= MAX_CONTENT_BOUNDARY_LEN) - return FALSE; - strncpy((char *)pDmInfo->boundary, (char *)pStart, pEnd - pStart); - pDmInfo->boundary[MAX_CONTENT_BOUNDARY_LEN - 1] = 0; - boundaryLen = strlen((char *)pDmInfo->boundary) + 2; /* 2 means: '\r' and '\n' */ - - pEnd += 2; /* skip the '\r' and '\n' */ - pStart = pEnd; - leftLen = pBufferEnd - pStart; - do { - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_7BIT; /* According RFC2045 chapter 6.1, the default value should be 7bit.*/ - strcpy((char *)pDmInfo->contentType, "text/plain"); /* According RFC2045 chapter 5.2, the default value should be "text/plain". */ - - /* Deal the header information */ - while ((('\r' != *pStart) || ('\n' != *(pStart + 1))) && pStart < pBufferEnd) { - pEnd = drm_strnstr(pStart, (uint8_t *)DRM_NEW_LINE_CRLF, leftLen); - if (NULL == pEnd) - return FALSE; - - if (0 != pDmInfo->deliveryType) { /* This means the delivery type has been confirmed */ - if (0 == strncmp((char *)pStart, HEADERS_TRANSFER_CODING, HEADERS_TRANSFER_CODING_LEN)) { - pStart += HEADERS_TRANSFER_CODING_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_7BIT, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_7BIT; - else if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_8BIT, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_8BIT; - else if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_BINARY, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_BINARY; - else if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_BASE64, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_BASE64; - else - return FALSE; /* Unknown transferCoding error */ - } else if (0 == drm_strnicmp(pStart, (uint8_t *)HEADERS_CONTENT_TYPE, HEADERS_CONTENT_TYPE_LEN)) { - pStart += HEADERS_CONTENT_TYPE_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - if (pEnd - pStart > 0) { - if ((pEnd - pStart) >= MAX_CONTENT_TYPE_LEN) - return FALSE; - strncpy((char *)pDmInfo->contentType, (char *)pStart, pEnd - pStart); - pDmInfo->contentType[pEnd - pStart] = '\0'; - } - } else if (0 == drm_strnicmp(pStart, (uint8_t *)HEADERS_CONTENT_ID, HEADERS_CONTENT_ID_LEN)) { - uint8_t tmpBuf[MAX_CONTENT_ID] = {0}; - uint8_t *pTmp; - - pStart += HEADERS_CONTENT_ID_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - /* error: more than one content id */ - if(drm_strnstr(pStart, (uint8_t*)HEADERS_CONTENT_ID, pBufferEnd - pStart)){ - ALOGD("drm_dmParser: error: more than one content id\r\n"); - return FALSE; - } - - status = DM_PARSING_CONTENT; /* can go here means that the rights object has been parsed. */ - - /* Change the format from <...> to cid:... */ - if (NULL != (pTmp = (uint8_t *)memchr((char *)pStart, '<', pEnd - pStart))) { - if ((pEnd - pTmp - 1) >= (int) sizeof(tmpBuf)) - return FALSE; - strncpy((char *)tmpBuf, (char *)(pTmp + 1), pEnd - pTmp - 1); - tmpBuf[MAX_CONTENT_ID - 1] = 0; - - if (NULL != (pTmp = (uint8_t *)memchr((char *)tmpBuf, '>', pEnd - pTmp - 1))) { - *pTmp = '\0'; - - memset(pDmInfo->contentID, 0, MAX_CONTENT_ID); - snprintf((char *)pDmInfo->contentID, MAX_CONTENT_ID, "%s%s", "cid:", (int8_t *)tmpBuf); - } - } - } - } else { /* First confirm delivery type, Forward_Lock, Combined Delivery or Separate Delivery */ - if (0 == drm_strnicmp(pStart, (uint8_t *)HEADERS_CONTENT_TYPE, HEADERS_CONTENT_TYPE_LEN)) { - pStart += HEADERS_CONTENT_TYPE_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - if (pEnd - pStart > 0) { - strncpy((char *)pDmInfo->contentType, (char *)pStart, pEnd - pStart); - pDmInfo->contentType[pEnd - pStart] = '\0'; - } - - if (0 == strcmp((char *)pDmInfo->contentType, DRM_MIME_TYPE_RIGHTS_XML)) { - pDmInfo->deliveryType = COMBINED_DELIVERY; - status = DM_PARSING_RIGHTS; - } - else if (0 == strcmp((char *)pDmInfo->contentType, DRM_MIME_TYPE_CONTENT)) { - pDmInfo->deliveryType = SEPARATE_DELIVERY_FL; - status = DM_PARSING_CONTENT; - } - else if (0 == pDmInfo->deliveryType) { - pDmInfo->deliveryType = FORWARD_LOCK; - status = DM_PARSING_CONTENT; - } - } - } - pEnd += 2; /* skip the '\r' and '\n' */ - pStart = pEnd; - leftLen = pBufferEnd - pStart; - } - pStart += 2; /* skip the second CRLF: "\r\n" */ - pEnd = pStart; - - /* Deal the content part, including rel or real content */ - while (leftLen > 0) { - if (NULL == (pEnd = memchr(pEnd, '\r', leftLen))) { - pEnd = pBufferEnd; - break; /* no boundary found */ - } - - leftLen = pBufferEnd - pEnd; - if (leftLen < boundaryLen) { - pEnd = pBufferEnd; - break; /* here means may be the boundary has been split */ - } - - if (('\n' == *(pEnd + 1)) && (0 == memcmp(pEnd + 2, pDmInfo->boundary, strlen((char *)pDmInfo->boundary)))) - break; /* find the boundary here */ - - pEnd++; - leftLen--; - } - - if (pEnd >= pBufferEnd) - contentLen = DRM_UNKNOWN_DATA_LEN; - else - contentLen = pEnd - pStart; - - switch(pDmInfo->deliveryType) { - case FORWARD_LOCK: - pDmInfo->contentLen = contentLen; - pDmInfo->contentOffset = pStart - buffer; - status = DM_PARSE_END; - break; - case COMBINED_DELIVERY: - if (DM_PARSING_RIGHTS == status) { - pDmInfo->rightsLen = contentLen; - pDmInfo->rightsOffset = pStart - buffer; - } else { - pDmInfo->contentLen = contentLen; - pDmInfo->contentOffset = pStart - buffer; - status = DM_PARSE_END; - } - break; - case SEPARATE_DELIVERY_FL: - { - T_DRM_DCF_Info dcfInfo; - uint8_t* pEncData = NULL; - - memset(&dcfInfo, 0, sizeof(T_DRM_DCF_Info)); - if (DRM_UNKNOWN_DATA_LEN == contentLen) - contentLen = pEnd - pStart; - if (FALSE == drm_dcfParser(pStart, contentLen, &dcfInfo, &pEncData)) - return FALSE; - - pDmInfo->contentLen = dcfInfo.EncryptedDataLen; - pDmInfo->contentOffset = pEncData - buffer; - strcpy((char *)pDmInfo->contentType, (char *)dcfInfo.ContentType); - strcpy((char *)pDmInfo->contentID, (char *)dcfInfo.ContentURI); - strcpy((char *)pDmInfo->rightsIssuer, (char *)dcfInfo.Rights_Issuer); - status = DM_PARSE_END; - } - break; - default: - return FALSE; - } - - if (DM_PARSING_RIGHTS == status) { - /* Here means the rights object data has been completed, boundary must exist */ - leftLen = pBufferEnd - pEnd; - pStart = drm_strnstr(pEnd, pDmInfo->boundary, leftLen); - if (NULL == pStart) - return FALSE; - leftLen = pBufferEnd - pStart; - pEnd = drm_strnstr(pStart, (uint8_t *)DRM_NEW_LINE_CRLF, leftLen); - if (NULL == pEnd) - return FALSE; /* only rights object, no media object, error */ - - pEnd += 2; /* skip the "\r\n" */ - pStart = pEnd; - } - } while (DM_PARSE_END != status); - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/parser/parser_rel.c b/media/libdrm/mobile1/src/parser/parser_rel.c deleted file mode 100644 index 537fa9ce4d6a635a9b276fdf02d2184446136897..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/parser/parser_rel.c +++ /dev/null @@ -1,663 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include -#include -#include -#include -#include -#include - -/* See parser_rel.h */ -int32_t drm_monthDays(int32_t year, int32_t month) -{ - switch (month) { - case 1: - case 3: - case 5: - case 7: - case 8: - case 10: - case 12: - return 31; - case 4: - case 6: - case 9: - case 11: - return 30; - case 2: - if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) - return 29; - else - return 28; - default: - return -1; - } -} - -int32_t drm_checkDate(int32_t year, int32_t month, int32_t day, - int32_t hour, int32_t min, int32_t sec) -{ - if (month >= 1 && month <= 12 && - day >= 1 && day <= drm_monthDays(year, month) && - hour >= 0 && hour <= 23 && - min >= 0 && min <= 59 && sec >= 0 && sec <= 59) - return 0; - else - return -1; -} - -static int32_t drm_getStartEndTime(uint8_t * pValue, int32_t valueLen, - T_DRM_DATETIME * dateTime) -{ - int32_t year, mon, day, hour, min, sec; - uint8_t pTmp[64] = {0}; - - strncpy((char *)pTmp, (char *)pValue, valueLen); - { - uint8_t * pHead = pTmp; - uint8_t * pEnd = NULL; - uint8_t tmpByte; - - /** get year */ - pEnd = (uint8_t *)strstr((char *)pHead, "-"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - year = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get month */ - pEnd = (uint8_t *)strstr((char *)pHead, "-"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - mon = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get day */ - pEnd = (uint8_t *)strstr((char *)pHead, "T"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - day = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get hour */ - pEnd = (uint8_t *)strstr((char *)pHead, ":"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - hour = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get minute */ - pEnd = (uint8_t *)strstr((char *)pHead, ":"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - min = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get second */ - sec = atoi((char *)pHead); - } - if (0 != drm_checkDate(year, mon, day, hour, min, sec)) - return FALSE; - - YMD_HMS_2_INT(year, mon, day, dateTime->date, hour, min, sec, - dateTime->time); - return TRUE; -} - -static int32_t drm_checkWhetherHasUnknowConstraint(uint8_t* drm_constrain) -{ - char* begin_constrain = ""; - char* end_constrain = ""; - char* constrain_begin = strstr((char*)drm_constrain,begin_constrain); - char* constrain_end = strstr((char*)drm_constrain,end_constrain); - uint32_t constrain_len = 0; - - if(NULL == constrain_begin) - return FALSE; - - if(NULL == constrain_end) - return TRUE; - - /* compute valid characters length */ - { - uint32_t constrain_begin_len = strlen(begin_constrain); - char* cur_pos = constrain_begin + constrain_begin_len; - - constrain_len = (constrain_end - constrain_begin) - constrain_begin_len; - - while(cur_pos < constrain_end){ - if(isspace(*cur_pos)) - constrain_len--; - - cur_pos++; - } - } - - /* check all constraints */ - { - #define DRM_ALL_CONSTRAINT_COUNT 5 - - int32_t i = 0; - int32_t has_datetime = FALSE; - int32_t has_start_or_end = FALSE; - - char* all_vaild_constraints[DRM_ALL_CONSTRAINT_COUNT][2] = { - {"",""}, - {"",""}, - {"",""}, - {"",""}, - {"",""} - }; - - for(i = 0; i < DRM_ALL_CONSTRAINT_COUNT; i++){ - char*start = strstr((char*)drm_constrain,all_vaild_constraints[i][0]); - - if(start && (start < constrain_end)){ - char* end = strstr((char*)drm_constrain,all_vaild_constraints[i][1]); - - if(end && (end < constrain_end)){ - if(0 == strncmp(all_vaild_constraints[i][0],"",strlen(""))){ - constrain_len -= strlen(all_vaild_constraints[i][0]); - constrain_len -= strlen(all_vaild_constraints[i][1]); - - if(0 == constrain_len) - return TRUE; - - has_datetime = TRUE; - continue; - } - - if((0 == strncmp(all_vaild_constraints[i][0],"",strlen(""))) - || (0 == strncmp(all_vaild_constraints[i][0],"",strlen("")))){ - if(FALSE == has_datetime) - return TRUE; - else - has_start_or_end = TRUE; - } - - constrain_len -= (end - start); - constrain_len -= strlen(all_vaild_constraints[i][1]); - - if(0 == constrain_len) - if(has_datetime != has_start_or_end) - return TRUE; - else - return FALSE; - } - else - return TRUE; - } - } - - if(has_datetime != has_start_or_end) - return TRUE; - - if(constrain_len) - return TRUE; - else - return FALSE; - } -} - -static int32_t drm_getRightValue(uint8_t * buffer, int32_t bufferLen, - T_DRM_Rights * ro, uint8_t * operation, - uint8_t oper_char) -{ - uint8_t *pBuf, *pValue; - uint8_t sProperty[256]; - int32_t valueLen; - int32_t year, mon, day, hour, min, sec; - T_DRM_Rights_Constraint *pConstraint; - int32_t *bIsAble; - uint8_t *ret = NULL; - int32_t flag = 0; - - if (operation == NULL) { - switch (oper_char) { - case REL_TAG_PLAY: - pConstraint = &(ro->PlayConstraint); - bIsAble = &(ro->bIsPlayable); - break; - case REL_TAG_DISPLAY: - pConstraint = &(ro->DisplayConstraint); - bIsAble = &(ro->bIsDisplayable); - break; - case REL_TAG_EXECUTE: - pConstraint = &(ro->ExecuteConstraint); - bIsAble = &(ro->bIsExecuteable); - break; - case REL_TAG_PRINT: - pConstraint = &(ro->PrintConstraint); - bIsAble = &(ro->bIsPrintable); - break; - default: - return FALSE; /* The input parm is err */ - } - } else { - if (strcmp((char *)operation, "play") == 0) { - pConstraint = &(ro->PlayConstraint); - bIsAble = &(ro->bIsPlayable); - } else if (strcmp((char *)operation, "display") == 0) { - pConstraint = &(ro->DisplayConstraint); - bIsAble = &(ro->bIsDisplayable); - } else if (strcmp((char *)operation, "execute") == 0) { - pConstraint = &(ro->ExecuteConstraint); - bIsAble = &(ro->bIsExecuteable); - } else if (strcmp((char *)operation, "print") == 0) { - pConstraint = &(ro->PrintConstraint); - bIsAble = &(ro->bIsPrintable); - } else - return FALSE; /* The input parm is err */ - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char); - ret = WBXML_DOM_getNode(buffer, bufferLen, sProperty); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s", - operation); - ret = XML_DOM_getNode(buffer, sProperty); - } - CHECK_VALIDITY(ret); - if (NULL == ret) - return TRUE; - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_NO_CONSTRAINT); /* If exit first assume have utter rights */ - flag = 1; - - if (operation == NULL) { /* If father element node is not exit then return */ - sprintf((char *)sProperty, "%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT); - ret = WBXML_DOM_getNode(buffer, bufferLen, sProperty); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint", - operation); - ret = XML_DOM_getNode(buffer, sProperty); - } - - CHECK_VALIDITY(ret); - if (ret == NULL) - return TRUE; - - if(TRUE == drm_checkWhetherHasUnknowConstraint(ret)) - return FALSE; - - *bIsAble = 0; - pConstraint->Indicator = DRM_NO_PERMISSION; /* If exit constraint assume have no rights */ - flag = 2; - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_INTERVAL); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:interval", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { /* If interval element exit then get the value */ - uint8_t pTmp[64] = {0}; - - strncpy((char *)pTmp, (char *)pValue, valueLen); - { - uint8_t * pHead = pTmp + 1; - uint8_t * pEnd = NULL; - uint8_t tmpChar; - - /** get year */ - pEnd = (uint8_t *)strstr((char *)pHead, "Y"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - year = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get month */ - pEnd = (uint8_t *)strstr((char *)pHead, "M"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - mon = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get day */ - pEnd = (uint8_t *)strstr((char *)pHead, "D"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - day = atoi((char *)pHead); - pHead = pEnd + 2; - *pEnd = tmpChar; - - /** get hour */ - pEnd = (uint8_t *)strstr((char *)pHead, "H"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - hour = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get minute */ - pEnd = (uint8_t *)strstr((char *)pHead, "M"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - min = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get second */ - pEnd = (uint8_t *)strstr((char *)pHead, "S"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - sec = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - } - - if (year < 0 || mon < 0 || day < 0 || hour < 0 - || min < 0 || sec < 0) - return FALSE; - YMD_HMS_2_INT(year, mon, day, pConstraint->Interval.date, hour, - min, sec, pConstraint->Interval.time); - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, - DRM_INTERVAL_CONSTRAINT); - flag = 3; - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_COUNT); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:count", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { /* If count element exit the get the value */ - uint8_t pTmp[16] = {0}; - int32_t i; - - for (i = 0; i < valueLen; i++) { /* Check the count format */ - if (0 == isdigit(*(pValue + i))) - return FALSE; - } - - strncpy((char *)pTmp, (char *)pValue, valueLen); - pConstraint->Count = atoi((char *)pTmp); - - if(0 == pConstraint->Count) - { - WRITE_RO_FLAG(*bIsAble, 0, pConstraint->Indicator, DRM_NO_PERMISSION); - } - else if( pConstraint->Count > 0) - { - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_COUNT_CONSTRAINT); - } - else /* < 0 */ - { - return FALSE; - } - - flag = 3; - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_DATETIME, REL_TAG_START); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:datetime\\o-dd:start", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { /* If start element exit then get the value */ - if (FALSE == - drm_getStartEndTime(pValue, valueLen, &pConstraint->StartTime)) - return FALSE; - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_START_TIME_CONSTRAINT); - flag = 3; - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_DATETIME, REL_TAG_END); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:datetime\\o-dd:end", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (FALSE == - drm_getStartEndTime(pValue, valueLen, &pConstraint->EndTime)) - return FALSE; - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_END_TIME_CONSTRAINT); - flag = 3; - } - - if (2 == flag) - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_NO_CONSTRAINT); /* If exit first assume have utter rights */ - return TRUE; -} - -/* See parser_rel.h */ -int32_t drm_relParser(uint8_t* buffer, int32_t bufferLen, int32_t Format, T_DRM_Rights* pRights) -{ - uint8_t *pBuf, *pValue; - uint8_t sProperty[256]; - int32_t valueLen; - - if (TYPE_DRM_RIGHTS_WBXML != Format && TYPE_DRM_RIGHTS_XML != Format) /* It is not the support parse format */ - return FALSE; - - if (TYPE_DRM_RIGHTS_XML == Format) { - /* Check whether it is a CD, and parse it using TYPE_DRM_RIGHTS_XML */ - if (NULL != drm_strnstr(buffer, (uint8_t *)HEADERS_CONTENT_ID, bufferLen)) - return FALSE; - - pBuf = - XML_DOM_getNodeValue(buffer, - (uint8_t *)"o-ex:rights\\o-ex:context\\o-dd:version", - &pValue, &valueLen); - CHECK_VALIDITY(pBuf); - - if (pBuf) { - if (valueLen > 8) /* Check version lenth */ - return FALSE; - - /* error version */ - if(strncmp(pValue,"1.0",valueLen)) - return FALSE; - - strncpy((char *)pRights->Version, (char *)pValue, valueLen); - } else - return FALSE; - - /* this means there is more than one version label in rights */ - if(strstr((char*)pBuf, "")) - return FALSE; - - pBuf = - XML_DOM_getNodeValue(buffer, - (uint8_t *)"o-ex:rights\\o-ex:agreement\\o-ex:asset\\ds:KeyInfo\\ds:KeyValue", - &pValue, &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { /* Get keyvalue */ - int32_t keyLen; - - if (24 != valueLen) - return FALSE; - - keyLen = drm_decodeBase64(NULL, 0, pValue, &valueLen); - if (keyLen < 0) - return FALSE; - - if (DRM_KEY_LEN != drm_decodeBase64(pRights->KeyValue, keyLen, pValue, &valueLen)) - return FALSE; - } - - pBuf = - XML_DOM_getNodeValue(buffer, - (uint8_t *)"o-ex:rights\\o-ex:agreement\\o-ex:asset\\o-ex:context\\o-dd:uid", - &pValue, &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (valueLen > DRM_UID_LEN) - return FALSE; - strncpy((char *)pRights->uid, (char *)pValue, valueLen); - pRights->uid[valueLen] = '\0'; - } else - return FALSE; - - /* this means there is more than one uid label in rights */ - if(strstr((char*)pBuf, "")) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"play", 0)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"display", 0)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"execute", 0)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"print", 0)) - return FALSE; - } else if (TYPE_DRM_RIGHTS_WBXML == Format) { - if (!REL_CHECK_WBXML_HEADER(buffer)) - return FALSE; - - sprintf((char *)sProperty, "%c%c%c", REL_TAG_RIGHTS, REL_TAG_CONTEXT, - REL_TAG_VERSION); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - CHECK_VALIDITY(pBuf); - - if (pBuf) { - if (valueLen > 8) /* Check version lenth */ - return FALSE; - strncpy((char *)pRights->Version, (char *)pValue, valueLen); - } else - return FALSE; - - sprintf((char *)sProperty, "%c%c%c%c%c", - REL_TAG_RIGHTS, REL_TAG_AGREEMENT, REL_TAG_ASSET, - REL_TAG_KEYINFO, REL_TAG_KEYVALUE); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (DRM_KEY_LEN != valueLen) - return FALSE; - memcpy(pRights->KeyValue, pValue, DRM_KEY_LEN); - memset(pValue, 0, DRM_KEY_LEN); /* Clean the KeyValue */ - } - - sprintf((char *)sProperty, "%c%c%c%c%c", - REL_TAG_RIGHTS, REL_TAG_AGREEMENT, REL_TAG_ASSET, - REL_TAG_CONTEXT, REL_TAG_UID); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (valueLen > DRM_UID_LEN) - return FALSE; - strncpy((char *)pRights->uid, (char *)pValue, valueLen); - pRights->uid[valueLen] = '\0'; - } else - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_PLAY)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_DISPLAY)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_EXECUTE)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_PRINT)) - return FALSE; - } - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/xml/xml_tinyparser.c b/media/libdrm/mobile1/src/xml/xml_tinyparser.c deleted file mode 100644 index 7580312d9e8ff78b3b9c89ad92f2055f82c985ab..0000000000000000000000000000000000000000 --- a/media/libdrm/mobile1/src/xml/xml_tinyparser.c +++ /dev/null @@ -1,834 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#include - -int32_t xml_errno; - -#ifdef XML_DOM_PARSER - -#define XML_IS_WHITESPACE(x) ((x) == '\t' || (x) == '\n' || (x) == ' ' || (x) == '\r') -#define XML_IS_NAMECHAR(ch) (isalpha(ch) || isdigit(ch) || ch ==':' || \ - ch == '_' || ch == '-' || ch =='.') - -static uint8_t *xml_ignore_blank(uint8_t *buffer) -{ - if (NULL == buffer) - return NULL; - - while (XML_IS_WHITESPACE(*buffer)) - buffer++; - - return buffer; -} - -static uint8_t *xml_goto_tagend(uint8_t *buffer) -{ - int32_t nameLen, valueLen; - uint8_t *name, *value; - - if (NULL == buffer) - return NULL; - - /* Ignore the start-tag */ - if (*buffer == '<') { - buffer++; - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) - return NULL; - } - - do { - if (NULL == (buffer = xml_ignore_blank(buffer))) - return NULL; - - if (*buffer == '>' || (*buffer == '/' && *(buffer + 1) == '>')) - return buffer; - - if (NULL == - XML_DOM_getAttr(buffer, &name, &nameLen, &value, &valueLen)) - return NULL; - - buffer = value + valueLen + 1; - } while (*buffer != '\0'); - - return NULL; -} - -static uint8_t *xml_match_tag(uint8_t *buffer) -{ - int32_t tagLen, tagType, bal; - - if (NULL == buffer) - return NULL; - - bal = 0; - do { - if (NULL == (buffer = XML_DOM_getTag(buffer, &tagLen, &tagType))) - return NULL; - - switch (tagType) { - case XML_TAG_SELF: - case XML_TAG_START: - if (NULL == (buffer = xml_goto_tagend(buffer + tagLen + 1))) - return NULL; - if (strncmp((char *)buffer, "/>", 2) == 0) { - buffer += 2; - } else { - bal++; - } - break; - - case XML_TAG_END: - if (bal <= 0) - return NULL; - buffer = buffer + tagLen + 2; - bal--; - break; - } - } while (bal != 0); - - return buffer; -} - -uint8_t *XML_DOM_getAttr(uint8_t *buffer, uint8_t **pName, int32_t *nameLen, - uint8_t **pValue, int32_t *valueLen) -{ - uint8_t charQuoted; - - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* Ignore the tag */ - if (*buffer == '<') { - buffer++; - /* Ignore the STag */ - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) - return NULL; - } - - if (NULL == (buffer = xml_ignore_blank(buffer))) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* Name */ - *pName = buffer; - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) { - XML_ERROR(XML_ERROR_ATTR_NAME); - return NULL; - } - *nameLen = buffer - *pName; - if (*nameLen <= 0) { - XML_ERROR(XML_ERROR_ATTR_NAME); - return NULL; - } - - /* '=' */ - buffer = xml_ignore_blank(buffer); - if (NULL == buffer || *buffer != '=') { - XML_ERROR(XML_ERROR_ATTR_MISSED_EQUAL); - return NULL; - } - - /* Value */ - buffer++; - buffer = xml_ignore_blank(buffer); - if (NULL == buffer || (*buffer != '"' && *buffer != '\'')) { - XML_ERROR(XML_ERROR_ATTR_VALUE); - return NULL; - } - charQuoted = *buffer++; - *pValue = buffer; - while (*buffer != '\0' && *buffer != charQuoted) - buffer++; - if (*buffer != charQuoted) { - XML_ERROR(XML_ERROR_ATTR_VALUE); - return NULL; - } - *valueLen = buffer - *pValue; - - XML_ERROR(XML_ERROR_OK); - - return buffer + 1; -} - -uint8_t *XML_DOM_getValue(uint8_t *buffer, uint8_t **pValue, int32_t *valueLen) -{ - uint8_t *pEnd; - - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* Ignore the STag */ - if (*buffer == '<') { - buffer++; - /* If it's an end_tag, no value should be returned */ - if (*buffer == '/') { - *valueLen = 0; - XML_ERROR(XML_ERROR_NOVALUE); - return NULL; - } - - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - if (NULL == (buffer = xml_goto_tagend(buffer))) { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - } - - /* node found */ - if (*buffer == '/') { - if (*(buffer + 1) != '>') { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - XML_ERROR(XML_ERROR_OK); - *valueLen = 0; - return buffer; - } - - if (*buffer == '>') - buffer++; - - if (NULL == (buffer = xml_ignore_blank(buffer))) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* the following is a tag instead of the value */ - if (*buffer == '<') { /* nono value, such as */ - buffer++; - if (*buffer != '/') { - XML_ERROR(XML_ERROR_ENDTAG); - return NULL; - } - *valueLen = 0; - XML_ERROR(XML_ERROR_OK); - return NULL; - } - - *pValue = buffer; - pEnd = NULL; - while (*buffer != '\0' && *buffer != '<') { - if (!XML_IS_WHITESPACE(*buffer)) - pEnd = buffer; - buffer++; - } - if (*buffer != '<' || pEnd == NULL) { - XML_ERROR(XML_ERROR_VALUE); - return NULL; - } - - *valueLen = pEnd - *pValue + 1; - - buffer++; - if (*buffer != '/') { - XML_ERROR(XML_ERROR_ENDTAG); - return NULL; - } - - XML_ERROR(XML_ERROR_OK); - - return buffer - 1; -} - -uint8_t *XML_DOM_getTag(uint8_t *buffer, int32_t *tagLen, int32_t *tagType) -{ - uint8_t *pStart; - - /* WARNING: comment is not supported in this verison */ - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - do { - while (*buffer != '<') { - if (*buffer == '\0') { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - if (*buffer == '\"' || *buffer == '\'') { - uint8_t charQuoted = *buffer; - buffer++; - while (*buffer != '\0' && *buffer != charQuoted) - buffer++; - if (*buffer == '\0') { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - } - buffer++; - } - buffer++; - } while (*buffer == '!' || *buffer == '?'); - - pStart = buffer - 1; - - if (*buffer == '/') { - buffer++; - *tagType = XML_TAG_END; - } else { - /* check here if it is self-end-tag */ - uint8_t *pCheck = xml_goto_tagend(pStart); - if (pCheck == NULL) { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - - if (*pCheck == '>') - *tagType = XML_TAG_START; - else if (strncmp((char *)pCheck, "/>", 2) == 0) - *tagType = XML_TAG_SELF; - else { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - } - - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - if (*tagType == XML_TAG_END) - *tagLen = buffer - pStart - 2; - else - *tagLen = buffer - pStart - 1; - - XML_ERROR(XML_ERROR_OK); - - return pStart; -} - -uint8_t *XML_DOM_getNode(uint8_t *buffer, const uint8_t *const node) -{ - uint8_t *pStart; - uint8_t buf[XML_MAX_PROPERTY_LEN + 2]; - uint8_t *nodeStr = buf; - uint8_t *retPtr = NULL; - int32_t tagLen, tagType; - uint8_t *lastNode = (uint8_t *)""; - - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - strncpy((char *)nodeStr, (char *)node, XML_MAX_PROPERTY_LEN); - strcat((char *)nodeStr, "\\"); - pStart = (uint8_t *)strchr((char *)nodeStr, '\\'); - - while (pStart != NULL) { - *pStart = '\0'; - - /* get the first start_tag from buffer */ - if (NULL == (buffer = XML_DOM_getTag(buffer, &tagLen, &tagType))) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - if (tagType == XML_TAG_END) { - if (0 == - strncmp((char *)lastNode, (char *)(buffer + 2), strlen((char *)lastNode))) - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - else - XML_ERROR(XML_ERROR_NO_START_TAG); - return NULL; - } - - /* wrong node, contiue to fetch the next node */ - if ((int32_t) strlen((char *)nodeStr) != tagLen - || strncmp((char *)nodeStr, (char *)(buffer + 1), tagLen) != 0) { - /* we should ignore all the middle code */ - buffer = xml_match_tag(buffer); - continue; - } - - retPtr = buffer; /* retPtr starts with '' */ - buffer += (tagLen + 1); - - if (tagType == XML_TAG_SELF) { - nodeStr = pStart + 1; - break; - } - - lastNode = nodeStr; - nodeStr = pStart + 1; - pStart = (uint8_t *)strchr((char *)nodeStr, '\\'); - } - - /* Check 5: nodeStr should be empty here */ - if (*nodeStr != '\0') { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - XML_ERROR(XML_ERROR_OK); - - return retPtr; -} - -uint8_t *XML_DOM_getNodeValue(uint8_t *buffer, uint8_t *node, - uint8_t **value, int32_t *valueLen) -{ - uint8_t *pStart; - uint8_t *lastTag; - - if (NULL == node || NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - lastTag = node + strlen((char *)node) - 1; - while (lastTag >= node && *lastTag != '\\') - lastTag--; - lastTag++; - - if (NULL == (pStart = XML_DOM_getNode(buffer, node))) - return NULL; - - pStart += (strlen((char *)lastTag) + 1); - - if (NULL == (pStart = xml_goto_tagend(pStart))) { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - - if (NULL == (pStart = XML_DOM_getValue(pStart, value, valueLen))) - return NULL; - - /* Check the end tag */ -#ifdef XML_DOM_CHECK_ENDTAG - if (strncmp((char *)pStart, "/>", 2) == 0) { - - } else if (strncmp((char *)lastTag, (char *)(pStart + 2), strlen((char *)lastTag)) != - 0) { - XML_ERROR(XML_ERROR_ENDTAG); - return NULL; - } -#endif - - XML_ERROR(XML_ERROR_OK); - - return *value; -} - -uint8_t *XML_DOM_getNextNode(uint8_t *buffer, uint8_t **pNodeName, int32_t *nodenameLen) -{ - int32_t tagType; - - if (NULL == buffer) - return NULL; - - do { - if (NULL == - (buffer = XML_DOM_getTag(buffer + 1, nodenameLen, &tagType))) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - } while (tagType == XML_TAG_END); - - *pNodeName = buffer + 1; - - XML_ERROR(XML_ERROR_OK); - - return buffer; -} - -#endif /* XML_DOM_PARSER */ - -#ifdef WBXML_DOM_PARSER - -#ifdef WBXML_OLD_VERSION -uint8_t *WBXML_DOM_getNode(uint8_t *buffer, int32_t bufferLen, - uint8_t *node) -{ - int32_t i = 0, j = 0; - - if (NULL == buffer || node == NULL) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - while (i < bufferLen) { - if (WBXML_GET_TAG(buffer[i]) == WBXML_GET_TAG(node[j])) { - j++; - if (node[j] == '\0') - break; - - /* Check if there is the content(it should have content) */ - if (!WBXML_HAS_CONTENT(buffer[i])) { - /*XML_ERROR(WBXML_ERROR_MISSED_CONTENT); */ - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - /* Ignore the attrib filed */ - if (WBXML_HAS_ATTR(buffer[i])) { - while (i < bufferLen && buffer[i] != WBXML_ATTR_END) - i++; - if (i >= bufferLen) - break; - } - } - i++; - - /* Ignore the content filed */ - if (buffer[i] == WBXML_STR_I) { - while (i < bufferLen && buffer[i] != WBXML_END) - i++; - if (i >= bufferLen) - break; - i++; - } - } - - if (i >= bufferLen) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - XML_ERROR(XML_ERROR_OK); - - return buffer + i + 1; -} - -uint8_t *WBXML_DOM_getNodeValue(uint8_t *buffer, int32_t bufferLen, - uint8_t *node, - uint8_t **value, int32_t *valueLen) -{ - int32_t i; - uint8_t *pEnd; - - *value = NULL; - *valueLen = 0; - - pEnd = buffer + bufferLen; - buffer = WBXML_DOM_getNode(buffer, bufferLen, node); - if (NULL == buffer) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - if (*buffer == WBXML_OPAUE) { - buffer++; - *valueLen = WBXML_GetUintVar(buffer, &i); - if (*valueLen < 0) { - XML_ERROR(WBXML_ERROR_MBUINT32); - return NULL; - } - buffer += i; - *value = buffer; - return *value; - } - - if (*buffer != WBXML_STR_I) { - XML_ERROR(WBXML_ERROR_MISSED_STARTTAG); - return NULL; - } - - buffer++; - - i = 0; - while ((buffer + i) < pEnd && buffer[i] != WBXML_END) - i++; - - if (buffer[i] != WBXML_END) { - XML_ERROR(WBXML_ERROR_MISSED_ENDTAG); - return NULL; - } - - *value = buffer; - *valueLen = i; - XML_ERROR(XML_ERROR_OK); - - return *value; -} -#endif /* WBXML_OLD_VERSION */ - -#define MAX_UINT_VAR_BYTE 4 -#define UINTVAR_INVALID -1 -int32_t WBXML_GetUintVar(const uint8_t *const buffer, int32_t *len) -{ - int32_t i, byteLen; - int32_t sum; - - byteLen = 0; - while ((buffer[byteLen] & 0x80) > 0 && byteLen < MAX_UINT_VAR_BYTE) - byteLen++; - - if (byteLen > MAX_UINT_VAR_BYTE) - return UINTVAR_INVALID; - - *len = byteLen + 1; - sum = buffer[byteLen]; - for (i = byteLen - 1; i >= 0; i--) - sum += ((buffer[i] & 0x7F) << 7 * (byteLen - i)); - - return sum; -} - -XML_BOOL WBXML_DOM_Init(WBXML * pWbxml, uint8_t *buffer, - int32_t bufferLen) -{ - int32_t num, len; - - pWbxml->End = buffer + bufferLen; - pWbxml->version = *buffer++; - if (UINTVAR_INVALID == (num = WBXML_GetUintVar(buffer, &len))) - return XML_FALSE; - buffer += len; - pWbxml->publicid = num; - if (UINTVAR_INVALID == (num = WBXML_GetUintVar(buffer, &len))) - return XML_FALSE; - buffer += len; - pWbxml->charset = num; - if (UINTVAR_INVALID == (num = WBXML_GetUintVar(buffer, &len))) - return XML_FALSE; - buffer += len; - pWbxml->strTable = buffer; - pWbxml->strTableLen = num; - buffer += num; - pWbxml->curPtr = pWbxml->Content = buffer; - pWbxml->depth = 0; - - return XML_TRUE; -} - -void WBXML_DOM_Rewind(WBXML * pWbxml) -{ - pWbxml->curPtr = pWbxml->Content; -} - -XML_BOOL WBXML_DOM_Eof(WBXML * pWbxml) -{ - if (pWbxml->curPtr > pWbxml->End) - return XML_TRUE; - - return XML_FALSE; -} - -uint8_t WBXML_DOM_GetTag(WBXML * pWbxml) -{ - uint8_t tagChar; - - if (pWbxml->curPtr > pWbxml->End) - return XML_EOF; - - tagChar = *pWbxml->curPtr; - pWbxml->curPtr++; - - if (WBXML_GET_TAG(tagChar) == WBXML_CONTENT_END) - pWbxml->depth--; - else - pWbxml->depth++; - - return tagChar; -} - -uint8_t WBXML_DOM_GetChar(WBXML * pWbxml) -{ - return *pWbxml->curPtr++; -} - -void WBXML_DOM_Seek(WBXML * pWbxml, int32_t offset) -{ - pWbxml->curPtr += offset; -} - -uint8_t WBXML_DOM_GetUIntVar(WBXML * pWbxml) -{ - int32_t num, len; - - num = WBXML_GetUintVar(pWbxml->curPtr, &len); - pWbxml->curPtr += len; - - return (uint8_t)num; -} - -#ifdef XML_TREE_STRUCTURE - -#ifdef DEBUG_MODE -static int32_t malloc_times = 0; -static int32_t free_times = 0; -void XML_PrintMallocInfo() -{ - printf("====XML_PrintMallocInfo====\n"); - printf(" Total malloc times:%d\n", malloc_times); - printf(" Total free times:%d\n", free_times); - printf("===========================\n"); -} -#endif - -void *xml_malloc(int32_t size) -{ -#ifdef DEBUG_MODE - malloc_times++; -#endif - return malloc(size); -} - -void xml_free(void *buffer) -{ -#ifdef DEBUG_MODE - free_times++; -#endif - free(buffer); -} - -XML_TREE *xml_tree_fillnode(uint8_t **buf, int32_t tagLen) -{ - XML_TREE *Tree; - uint8_t *pAttr, *pName, *pValue; - int32_t nameLen, valueLen; - uint8_t *buffer = *buf; - - if (NULL == (Tree = (XML_TREE *) xml_malloc(sizeof(XML_TREE)))) - return NULL; - memset(Tree, 0, sizeof(XML_TREE)); - - strncpy((char *)Tree->tag, (char *)++buffer, tagLen); - buffer += tagLen; - pAttr = buffer; - - /* attribute */ - while (NULL != - (pAttr = - XML_DOM_getAttr(pAttr, &pName, &nameLen, &pValue, - &valueLen))) { - XML_TREE_ATTR *attr; - if (NULL == - (attr = (XML_TREE_ATTR *) xml_malloc(sizeof(XML_TREE_ATTR)))) - return NULL; - memset(attr, 0, sizeof(XML_TREE_ATTR)); - strncpy((char *)attr->name, (char *)pName, nameLen); - strncpy((char *)attr->value, (char *)pValue, valueLen); - buffer = pValue + valueLen + 1; - - if (NULL != Tree->attr) // no attribute now - Tree->last_attr->next = attr; - else - Tree->attr = attr; - Tree->last_attr = attr; - } - - /* value */ - pAttr = XML_DOM_getValue(buffer, &pValue, &valueLen); - if (pAttr != NULL && valueLen > 0) { - strncpy((char *)Tree->value, (char *)pValue, valueLen); - buffer = pValue + valueLen; - } - - *buf = buffer; - return Tree; -} - -XML_TREE *XML_makeTree(uint8_t **buf) -{ - uint8_t *pBuf; - int32_t valueLen, tagType; - uint8_t *buffer = *buf; - XML_TREE *TreeHead = NULL; - - if (NULL == (buffer = XML_DOM_getTag(buffer, &valueLen, &tagType))) - return NULL; - if (XML_TAG_END == tagType) - return NULL; - if (NULL == (TreeHead = xml_tree_fillnode(&buffer, valueLen))) - return NULL; - if (XML_TAG_SELF == tagType) { - *buf = buffer; - return TreeHead; - } - - do { - if (NULL == (pBuf = XML_DOM_getTag(buffer, &valueLen, &tagType))) - return NULL; - - switch (tagType) { - case XML_TAG_SELF: - case XML_TAG_START: - if (NULL == TreeHead->child) - TreeHead->child = XML_makeTree(&buffer); - else if (NULL == TreeHead->child->last_brother) { - TreeHead->child->brother = XML_makeTree(&buffer); - TreeHead->child->last_brother = TreeHead->child->brother; - } else { - TreeHead->child->last_brother->brother = - XML_makeTree(&buffer); - TreeHead->child->last_brother = - TreeHead->child->last_brother->brother; - } - break; - case XML_TAG_END: - *buf = pBuf; - return TreeHead; - } - buffer++; - } while (1); -} - -void XML_freeTree(XML_TREE * pTree) -{ - XML_TREE *p, *pNext; - XML_TREE_ATTR *pa, *lastpa; - - if (NULL == pTree) - return; - - p = pTree->brother; - while (NULL != p) { - pNext = p->brother; - p->brother = NULL; - XML_freeTree(p); - p = pNext; - } - - if (NULL != pTree->child) - XML_freeTree(pTree->child); - - pa = pTree->attr; - while (NULL != pa) { - lastpa = pa; - pa = pa->next; - xml_free(lastpa); - } - xml_free(pTree); -} - -#endif /* XML_TREE_STRUCTURE */ - -#endif /* WBXML_DOM_PARSER */ diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp index 73768feddcab520fd74c7aa942014c3719681cfc..84dad8ca70ee1f8ad6b578e424c779a9affe2fbd 100644 --- a/media/mca/filterfw/native/core/gl_env.cpp +++ b/media/mca/filterfw/native/core/gl_env.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace android { namespace filterfw { @@ -160,9 +162,9 @@ bool GLEnv::InitWithNewContext() { } // Create dummy surface using a GLConsumer - surfaceTexture_ = new GLConsumer(0); - window_ = new Surface(static_cast >( - surfaceTexture_->getBufferQueue())); + sp bq = new BufferQueue(); + surfaceTexture_ = new GLConsumer(bq, 0); + window_ = new Surface(static_cast >(bq)); surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL); if (CheckEGLError("eglCreateWindowSurface")) return false; diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h index 81e1e9dcb7a2a24096538f8c8f83934a3b2317fd..a709638f1c34aa344bf3b5429433603a40902ecc 100644 --- a/media/mca/filterfw/native/core/gl_env.h +++ b/media/mca/filterfw/native/core/gl_env.h @@ -31,6 +31,9 @@ #include namespace android { + +class GLConsumer; + namespace filterfw { class ShaderProgram; diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk index c9afa19f274d67096ffc756d07bf59cc6db72c5e..1e6b2e77029e423eab588361883ad8c56d212741 100644 --- a/media/tests/MediaFrameworkTest/Android.mk +++ b/media/tests/MediaFrameworkTest/Android.mk @@ -7,7 +7,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_JAVA_LIBRARIES := android.test.runner -LOCAL_STATIC_JAVA_LIBRARIES := easymocklib +LOCAL_STATIC_JAVA_LIBRARIES := easymocklib mockito-target LOCAL_PACKAGE_NAME := mediaframeworktest diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index b6987058dc9ddf1ca83fbacfbbf2aa5cf5f83467..91ee2c60cef838fedff8f5facdc625338fe6e7df 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -71,4 +71,9 @@ android:label="Media Power tests InstrumentationRunner"> + + + diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..7751fccd265f7d115b09aa76626731df67428604 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java @@ -0,0 +1,72 @@ +/* + * 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.mediaframeworktest; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; + +import com.android.mediaframeworktest.integration.CameraBinderTest; +import com.android.mediaframeworktest.integration.CameraDeviceBinderTest; + +import junit.framework.TestSuite; + +/** + * Instrumentation Test Runner for all media framework integration tests. + * + * Running all tests: + * + * adb shell am instrument -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner + */ + +public class MediaFrameworkIntegrationTestRunner extends InstrumentationTestRunner { + + private static final String TAG = "MediaFrameworkIntegrationTestRunner"; + + public static int mCameraId = 0; + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(CameraBinderTest.class); + suite.addTestSuite(CameraDeviceBinderTest.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return MediaFrameworkIntegrationTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + String cameraId = (String) icicle.get("camera_id"); + if (cameraId != null) { + try { + Log.v(TAG, + String.format("Reading camera_id from icicle: '%s'", cameraId)); + mCameraId = Integer.parseInt(cameraId); + } + catch (NumberFormatException e) { + Log.e(TAG, String.format("Failed to convert camera_id to integer")); + } + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java index 92ac9eb097662e1cc81f64ad82a7098d939de9e2..cbb66428b07db3e07f58baf478fb762c0e3bacfa 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java @@ -50,7 +50,7 @@ import android.test.InstrumentationTestSuite; * Running all tests: * * adb shell am instrument \ - * -w com.android.smstests.MediaPlayerInstrumentationTestRunner + * -w com.android.mediaframeworktest/.MediaFrameworkTestRunner */ public class MediaFrameworkTestRunner extends InstrumentationTestRunner { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java index 62af3f32a07924bfaeacbf592a2de2fec592f28f..64b12b7648ce0d6753468ff4e9bc8c2dd1f540c1 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java @@ -48,6 +48,8 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { addMediaRecorderStateUnitTests(suite); addMediaPlayerStateUnitTests(suite); addMediaScannerUnitTests(suite); + addCameraUnitTests(suite); + addImageReaderTests(suite); return suite; } @@ -56,6 +58,18 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { return MediaFrameworkUnitTestRunner.class.getClassLoader(); } + private void addCameraUnitTests(TestSuite suite) { + suite.addTestSuite(CameraUtilsDecoratorTest.class); + suite.addTestSuite(CameraUtilsRuntimeExceptionTest.class); + suite.addTestSuite(CameraUtilsUncheckedThrowTest.class); + suite.addTestSuite(CameraUtilsBinderDecoratorTest.class); + suite.addTestSuite(CameraMetadataTest.class); + } + + private void addImageReaderTests(TestSuite suite) { + suite.addTestSuite(ImageReaderTest.class); + } + // Running all unit tests checking the state machine may be time-consuming. private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) { suite.addTestSuite(MediaMetadataRetrieverTest.class); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java index 2f864d7fa906f6565277c72d4102e517ab10bcdb..7f23ba576fbbc89eabf73b99a9e9b9ea7ddc9c13 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java @@ -36,7 +36,12 @@ import java.io.*; /** * Junit / Instrumentation test case for the camera api - + * + * To run only tests in this class: + * + * adb shell am instrument \ + * -e class com.android.mediaframeworktest.functional.CameraTest \ + * -w com.android.mediaframeworktest/.MediaFrameworkTestRunner */ public class CameraTest extends ActivityInstrumentationTestCase { private String TAG = "CameraTest"; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d157478711032dac56d50bdc71db2cb496198a90 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -0,0 +1,237 @@ +/* + * 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.mediaframeworktest.integration; + +import android.hardware.CameraInfo; +import android.hardware.ICamera; +import android.hardware.ICameraClient; +import android.hardware.ICameraServiceListener; +import android.hardware.IProCameraCallbacks; +import android.hardware.IProCameraUser; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.BinderHolder; +import android.hardware.camera2.utils.CameraBinderDecorator; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +/** + *

        + * Junit / Instrumentation test case for the camera2 api + *

        + *

        + * To run only tests in this class: + *

        + * + *
        + * adb shell am instrument \
        + *   -e class com.android.mediaframeworktest.integration.CameraBinderTest \
        + *   -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner
        + * 
        + */ +public class CameraBinderTest extends AndroidTestCase { + static String TAG = "CameraBinderTest"; + + protected CameraBinderTestUtils mUtils; + + public CameraBinderTest() { + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mUtils = new CameraBinderTestUtils(getContext()); + } + + @SmallTest + public void testNumberOfCameras() throws Exception { + + int numCameras = mUtils.getCameraService().getNumberOfCameras(); + assertTrue("At least this many cameras: " + mUtils.getGuessedNumCameras(), + numCameras >= mUtils.getGuessedNumCameras()); + Log.v(TAG, "Number of cameras " + numCameras); + } + + @SmallTest + public void testCameraInfo() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + CameraInfo info = new CameraInfo(); + info.info.facing = -1; + info.info.orientation = -1; + + assertTrue( + "Camera service returned info for camera " + cameraId, + mUtils.getCameraService().getCameraInfo(cameraId, info) == + CameraBinderTestUtils.NO_ERROR); + assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1); + assertTrue("Orientation was not set for camera " + cameraId, + info.info.orientation != -1); + + Log.v(TAG, "Camera " + cameraId + " info: facing " + info.info.facing + + ", orientation " + info.info.orientation); + } + } + + static abstract class DummyBase extends Binder implements android.os.IInterface { + @Override + public IBinder asBinder() { + return this; + } + } + + static class DummyCameraClient extends DummyBase implements ICameraClient { + } + + @SmallTest + public void testConnect() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + ICameraClient dummyCallbacks = new DummyCameraClient(); + + String clientPackageName = getContext().getPackageName(); + + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connect(dummyCallbacks, cameraId, clientPackageName, + CameraBinderTestUtils.USE_CALLING_UID, holder); + ICamera cameraUser = ICamera.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); + + Log.v(TAG, String.format("Camera %s connected", cameraId)); + + cameraUser.disconnect(); + } + } + + static class DummyProCameraCallbacks extends DummyBase implements IProCameraCallbacks { + } + + @SmallTest + public void testConnectPro() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + IProCameraCallbacks dummyCallbacks = new DummyProCameraCallbacks(); + + String clientPackageName = getContext().getPackageName(); + + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connectPro(dummyCallbacks, cameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + IProCameraUser cameraUser = IProCameraUser.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); + + Log.v(TAG, String.format("Camera %s connected", cameraId)); + + cameraUser.disconnect(); + } + } + + static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { + + @Override + public void onCameraError(int errorCode) { + } + + @Override + public void onCameraIdle() { + } + + @Override + public void onCaptureStarted(int requestId, long timestamp) { + } + + @Override + public void onResultReceived(int frameId, CameraMetadataNative result) + throws RemoteException { + } + } + + @SmallTest + public void testConnectDevice() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + ICameraDeviceCallbacks dummyCallbacks = new DummyCameraDeviceCallbacks(); + + String clientPackageName = getContext().getPackageName(); + + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connectDevice(dummyCallbacks, cameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + ICameraDeviceUser cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); + + Log.v(TAG, String.format("Camera %s connected", cameraId)); + + cameraUser.disconnect(); + } + } + + static class DummyCameraServiceListener extends ICameraServiceListener.Stub { + @Override + public void onStatusChanged(int status, int cameraId) + throws RemoteException { + Log.v(TAG, String.format("Camera %d has status changed to 0x%x", cameraId, status)); + } + } + + /** + *
        +     * adb shell am instrument \
        +     *   -e class 'com.android.mediaframeworktest.integration.CameraBinderTest#testAddRemoveListeners' \
        +     *   -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner
        +     * 
        + */ + @SmallTest + public void testAddRemoveListeners() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + ICameraServiceListener listener = new DummyCameraServiceListener(); + + assertTrue( + "Listener was removed before added", + mUtils.getCameraService().removeListener(listener) == + CameraBinderTestUtils.BAD_VALUE); + + assertTrue("Listener was not added", + mUtils.getCameraService().addListener(listener) == + CameraBinderTestUtils.NO_ERROR); + assertTrue( + "Listener was wrongly added again", + mUtils.getCameraService().addListener(listener) == + CameraBinderTestUtils.ALREADY_EXISTS); + + assertTrue( + "Listener was not removed", + mUtils.getCameraService().removeListener(listener) == + CameraBinderTestUtils.NO_ERROR); + assertTrue( + "Listener was wrongly removed again", + mUtils.getCameraService().removeListener(listener) == + CameraBinderTestUtils.BAD_VALUE); + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..1be2a6261fccfff30df274e5d674eb1753ce0d34 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java @@ -0,0 +1,93 @@ + +package com.android.mediaframeworktest.integration; + +import static org.junit.Assert.assertNotNull; + +import android.content.Context; +import android.content.pm.FeatureInfo; +import android.content.pm.PackageManager; +import android.hardware.ICameraService; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +public class CameraBinderTestUtils { + private final ICameraService mCameraService; + private int mGuessedNumCameras; + + static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; + + protected static final int USE_CALLING_UID = -1; + protected static final int BAD_VALUE = -22; + protected static final int INVALID_OPERATION = -38; + protected static final int ALREADY_EXISTS = -17; + public static final int NO_ERROR = 0; + private final Context mContext; + + public CameraBinderTestUtils(Context context) { + + mContext = context; + + guessNumCameras(); + + IBinder cameraServiceBinder = ServiceManager + .getService(CameraBinderTestUtils.CAMERA_SERVICE_BINDER_NAME); + assertNotNull("Camera service IBinder should not be null", cameraServiceBinder); + + this.mCameraService = ICameraService.Stub.asInterface(cameraServiceBinder); + assertNotNull("Camera service should not be null", getCameraService()); + } + + private void guessNumCameras() { + + /** + * Why do we need this? This way we have no dependency on getNumCameras + * actually working. On most systems there are only 0, 1, or 2 cameras, + * and this covers that 'usual case'. On other systems there might be 3+ + * cameras, but this will at least check the first 2. + */ + this.mGuessedNumCameras = 0; + + // Front facing camera + if (CameraBinderTestUtils.isFeatureAvailable(mContext, + PackageManager.FEATURE_CAMERA_FRONT)) { + this.mGuessedNumCameras = getGuessedNumCameras() + 1; + } + + // Back facing camera + if (CameraBinderTestUtils.isFeatureAvailable(mContext, + PackageManager.FEATURE_CAMERA)) { + this.mGuessedNumCameras = getGuessedNumCameras() + 1; + } + + // Any facing camera + if (getGuessedNumCameras() == 0 + && CameraBinderTestUtils.isFeatureAvailable(mContext, + PackageManager.FEATURE_CAMERA_ANY)) { + this.mGuessedNumCameras = getGuessedNumCameras() + 1; + } + + Log.v(CameraBinderTest.TAG, "Guessing there are at least " + getGuessedNumCameras() + + " cameras"); + } + + final static public boolean isFeatureAvailable(Context context, String feature) { + final PackageManager packageManager = context.getPackageManager(); + final FeatureInfo[] featuresList = packageManager.getSystemAvailableFeatures(); + for (FeatureInfo f : featuresList) { + if (f.name != null && f.name.equals(feature)) { + return true; + } + } + + return false; + } + + ICameraService getCameraService() { + return mCameraService; + } + + int getGuessedNumCameras() { + return mGuessedNumCameras; + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..43ebef4d08c3c0a763bd630d17578637f28f2698 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -0,0 +1,461 @@ +/* + * 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.mediaframeworktest.integration; + +import android.graphics.ImageFormat; +import android.graphics.SurfaceTexture; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.BinderHolder; +import android.media.Image; +import android.media.ImageReader; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.RemoteException; +import android.os.SystemClock; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; +import android.view.Surface; + +import static android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW; + +import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner; + +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentCaptor; +import static org.mockito.Mockito.*; + +public class CameraDeviceBinderTest extends AndroidTestCase { + private static String TAG = "CameraDeviceBinderTest"; + // Number of streaming callbacks need to check. + private static int NUM_CALLBACKS_CHECKED = 10; + // Wait for capture result timeout value: 1500ms + private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1500; + // Wait for flush timeout value: 1000ms + private final static int WAIT_FOR_FLUSH_TIMEOUT_MS = 1000; + // Wait for idle timeout value: 2000ms + private final static int WAIT_FOR_IDLE_TIMEOUT_MS = 2000; + // Wait while camera device starts working on requests + private final static int WAIT_FOR_WORK_MS = 300; + // Default size is VGA, which is mandatory camera supported image size by CDD. + private static final int DEFAULT_IMAGE_WIDTH = 640; + private static final int DEFAULT_IMAGE_HEIGHT = 480; + private static final int MAX_NUM_IMAGES = 5; + + private int mCameraId; + private ICameraDeviceUser mCameraUser; + private CameraBinderTestUtils mUtils; + private ICameraDeviceCallbacks.Stub mMockCb; + private Surface mSurface; + private HandlerThread mHandlerThread; + private Handler mHandler; + ImageReader mImageReader; + + public CameraDeviceBinderTest() { + } + + private class ImageDropperListener implements ImageReader.OnImageAvailableListener { + + @Override + public void onImageAvailable(ImageReader reader) { + Image image = reader.acquireNextImage(); + if (image != null) image.close(); + } + } + + public class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { + + @Override + public void onCameraError(int errorCode) { + } + + @Override + public void onCameraIdle() { + } + + @Override + public void onCaptureStarted(int requestId, long timestamp) { + } + + @Override + public void onResultReceived(int frameId, CameraMetadataNative result) { + } + } + + class IsMetadataNotEmpty extends ArgumentMatcher { + @Override + public boolean matches(Object obj) { + return !((CameraMetadataNative) obj).isEmpty(); + } + } + + private void createDefaultSurface() { + mImageReader = + ImageReader.newInstance(DEFAULT_IMAGE_WIDTH, + DEFAULT_IMAGE_HEIGHT, + ImageFormat.YUV_420_888, + MAX_NUM_IMAGES); + mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler); + mSurface = mImageReader.getSurface(); + } + + private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception { + CameraMetadataNative metadata = new CameraMetadataNative(); + assertTrue(metadata.isEmpty()); + + int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + assertFalse(metadata.isEmpty()); + + CaptureRequest.Builder request = new CaptureRequest.Builder(metadata); + assertFalse(request.isEmpty()); + assertFalse(metadata.isEmpty()); + if (needStream) { + int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, + /* ignored */30, mSurface); + assertEquals(0, streamId); + request.addTarget(mSurface); + } + return request; + } + + private int submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception { + int requestId = mCameraUser.submitRequest(request, streaming); + assertTrue("Request IDs should be non-negative", requestId >= 0); + return requestId; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + /** + * Workaround for mockito and JB-MR2 incompatibility + * + * Avoid java.lang.IllegalArgumentException: dexcache == null + * https://code.google.com/p/dexmaker/issues/detail?id=2 + */ + System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); + mUtils = new CameraBinderTestUtils(getContext()); + + // This cannot be set in the constructor, since the onCreate isn't + // called yet + mCameraId = MediaFrameworkIntegrationTestRunner.mCameraId; + + ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks(); + + String clientPackageName = getContext().getPackageName(); + + mMockCb = spy(dummyCallbacks); + + BinderHolder holder = new BinderHolder(); + mUtils.getCameraService().connectDevice(mMockCb, mCameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + mCameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser); + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + createDefaultSurface(); + + Log.v(TAG, String.format("Camera %s connected", mCameraId)); + } + + @Override + protected void tearDown() throws Exception { + mCameraUser.disconnect(); + mCameraUser = null; + mSurface.release(); + mImageReader.close(); + mHandlerThread.quitSafely(); + } + + @SmallTest + public void testCreateDefaultRequest() throws Exception { + CameraMetadataNative metadata = new CameraMetadataNative(); + assertTrue(metadata.isEmpty()); + + int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + assertFalse(metadata.isEmpty()); + + } + + @SmallTest + public void testCreateStream() throws Exception { + int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, /* ignored */30, + mSurface); + assertEquals(0, streamId); + + assertEquals(CameraBinderTestUtils.ALREADY_EXISTS, + mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface)); + + assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId)); + } + + @SmallTest + public void testDeleteInvalidStream() throws Exception { + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(-1)); + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0)); + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(1)); + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0xC0FFEE)); + } + + @SmallTest + public void testCreateStreamTwo() throws Exception { + + // Create first stream + int streamId = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, + mSurface); + assertEquals(0, streamId); + + assertEquals(CameraBinderTestUtils.ALREADY_EXISTS, + mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface)); + + // Create second stream with a different surface. + SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0); + surfaceTexture.setDefaultBufferSize(640, 480); + Surface surface2 = new Surface(surfaceTexture); + + int streamId2 = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, + surface2); + assertEquals(1, streamId2); + + // Clean up streams + assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId)); + assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId2)); + } + + @SmallTest + public void testSubmitBadRequest() throws Exception { + + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false); + CaptureRequest request1 = builder.build(); + int status = mCameraUser.submitRequest(request1, /* streaming */false); + assertEquals("Expected submitRequest to return BAD_VALUE " + + "since we had 0 surface targets set.", CameraBinderTestUtils.BAD_VALUE, status); + + builder.addTarget(mSurface); + CaptureRequest request2 = builder.build(); + status = mCameraUser.submitRequest(request2, /* streaming */false); + assertEquals("Expected submitRequest to return BAD_VALUE since " + + "the target surface wasn't registered with createStream.", + CameraBinderTestUtils.BAD_VALUE, status); + } + + @SmallTest + public void testSubmitGoodRequest() throws Exception { + + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true); + CaptureRequest request = builder.build(); + + // Submit valid request twice. + int requestId1 = submitCameraRequest(request, /* streaming */false); + int requestId2 = submitCameraRequest(request, /* streaming */false); + assertNotSame("Request IDs should be unique for multiple requests", requestId1, requestId2); + + } + + @SmallTest + public void testSubmitStreamingRequest() throws Exception { + + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true); + + CaptureRequest request = builder.build(); + + // Submit valid request once (non-streaming), and another time + // (streaming) + int requestId1 = submitCameraRequest(request, /* streaming */false); + + int requestIdStreaming = submitCameraRequest(request, /* streaming */true); + assertNotSame("Request IDs should be unique for multiple requests", requestId1, + requestIdStreaming); + + int status = mCameraUser.cancelRequest(-1); + assertEquals("Invalid request IDs should not be cancellable", + CameraBinderTestUtils.BAD_VALUE, status); + + status = mCameraUser.cancelRequest(requestId1); + assertEquals("Non-streaming request IDs should not be cancellable", + CameraBinderTestUtils.BAD_VALUE, status); + + status = mCameraUser.cancelRequest(requestIdStreaming); + assertEquals("Streaming request IDs should be cancellable", CameraBinderTestUtils.NO_ERROR, + status); + + } + + @SmallTest + public void testCameraInfo() throws RemoteException { + CameraMetadataNative info = new CameraMetadataNative(); + + int status = mCameraUser.getCameraInfo(/*out*/info); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + assertFalse(info.isEmpty()); + assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)); + } + + @SmallTest + public void testCameraCharacteristics() throws RemoteException { + CameraMetadataNative info = new CameraMetadataNative(); + + int status = mUtils.getCameraService().getCameraCharacteristics(mCameraId, /*out*/info); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + assertFalse(info.isEmpty()); + assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)); + } + + @SmallTest + public void testWaitUntilIdle() throws Exception { + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true); + int requestIdStreaming = submitCameraRequest(builder.build(), /* streaming */true); + + // Test Bad case first: waitUntilIdle when there is active repeating request + int status = mCameraUser.waitUntilIdle(); + assertEquals("waitUntilIdle is invalid operation when there is active repeating request", + CameraBinderTestUtils.INVALID_OPERATION, status); + + // Test good case, waitUntilIdle when there is no active repeating request + status = mCameraUser.cancelRequest(requestIdStreaming); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + status = mCameraUser.waitUntilIdle(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + } + + @SmallTest + public void testCaptureResultCallbacks() throws Exception { + IsMetadataNotEmpty matcher = new IsMetadataNotEmpty(); + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + // Test both single request and streaming request. + int requestId1 = submitCameraRequest(request, /* streaming */false); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived( + eq(requestId1), + argThat(matcher)); + + int streamingId = submitCameraRequest(request, /* streaming */true); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED)) + .onResultReceived( + eq(streamingId), + argThat(matcher)); + } + + @SmallTest + public void testCaptureStartedCallbacks() throws Exception { + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + ArgumentCaptor timestamps = ArgumentCaptor.forClass(Long.class); + + // Test both single request and streaming request. + int requestId1 = submitCameraRequest(request, /* streaming */false); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted( + eq(requestId1), + anyLong()); + + int streamingId = submitCameraRequest(request, /* streaming */true); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED)) + .onCaptureStarted( + eq(streamingId), + timestamps.capture()); + + long timestamp = 0; // All timestamps should be larger than 0. + for (Long nextTimestamp : timestamps.getAllValues()) { + Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp); + assertTrue("Captures are out of order", timestamp < nextTimestamp); + timestamp = nextTimestamp; + } + } + + @SmallTest + public void testIdleCallback() throws Exception { + int status; + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + // Try streaming + int streamingId = submitCameraRequest(request, /* streaming */true); + + // Wait a bit to fill up the queue + SystemClock.sleep(WAIT_FOR_WORK_MS); + + // Cancel and make sure we eventually quiesce + status = mCameraUser.cancelRequest(streamingId); + + verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onCameraIdle(); + + // Submit a few capture requests + int requestId1 = submitCameraRequest(request, /* streaming */false); + int requestId2 = submitCameraRequest(request, /* streaming */false); + int requestId3 = submitCameraRequest(request, /* streaming */false); + int requestId4 = submitCameraRequest(request, /* streaming */false); + int requestId5 = submitCameraRequest(request, /* streaming */false); + + // And wait for more idle + verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onCameraIdle(); + + } + + @SmallTest + public void testFlush() throws Exception { + int status; + + // Initial flush should work + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + // Then set up a stream + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + // Flush should still be a no-op, really + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + // Submit a few capture requests + int requestId1 = submitCameraRequest(request, /* streaming */false); + int requestId2 = submitCameraRequest(request, /* streaming */false); + int requestId3 = submitCameraRequest(request, /* streaming */false); + int requestId4 = submitCameraRequest(request, /* streaming */false); + int requestId5 = submitCameraRequest(request, /* streaming */false); + + // Then flush and wait for idle + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onCameraIdle(); + + // Now a streaming request + int streamingId = submitCameraRequest(request, /* streaming */true); + + // Wait a bit to fill up the queue + SystemClock.sleep(WAIT_FOR_WORK_MS); + + // Then flush and wait for the idle callback + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onCameraIdle(); + + // TODO: When errors are hooked up, count that errors + successful + // requests equal to 5. + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java index 074bfe48e516534a9b25dd9aee76a9a515f201e7..7b2a20ea4dbd83271aba42ffa5c58bdbd6882e8f 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java @@ -61,6 +61,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2 + * adb shell am instrument \ + * -e class 'com.android.mediaframeworktest.unit.CameraMetadataTest' \ + * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner + * + */ +public class CameraMetadataTest extends junit.framework.TestCase { + + CameraMetadataNative mMetadata; + Parcel mParcel; + + // Sections + static final int ANDROID_COLOR_CORRECTION = 0; + static final int ANDROID_CONTROL = 1; + + // Section starts + static final int ANDROID_COLOR_CORRECTION_START = ANDROID_COLOR_CORRECTION << 16; + static final int ANDROID_CONTROL_START = ANDROID_CONTROL << 16; + + // Tags + static final int ANDROID_COLOR_CORRECTION_MODE = ANDROID_COLOR_CORRECTION_START; + static final int ANDROID_COLOR_CORRECTION_TRANSFORM = ANDROID_COLOR_CORRECTION_START + 1; + static final int ANDROID_COLOR_CORRECTION_GAINS = ANDROID_COLOR_CORRECTION_START + 2; + + static final int ANDROID_CONTROL_AE_ANTIBANDING_MODE = ANDROID_CONTROL_START; + static final int ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION = ANDROID_CONTROL_START + 1; + + @Override + public void setUp() { + mMetadata = new CameraMetadataNative(); + mParcel = Parcel.obtain(); + } + + @Override + public void tearDown() throws Exception { + mMetadata = null; + + mParcel.recycle(); + mParcel = null; + } + + @SmallTest + public void testNew() { + assertEquals(0, mMetadata.getEntryCount()); + assertTrue(mMetadata.isEmpty()); + } + + @SmallTest + public void testGetTagFromKey() { + + // Test success + + assertEquals(ANDROID_COLOR_CORRECTION_MODE, + CameraMetadataNative.getTag("android.colorCorrection.mode")); + assertEquals(ANDROID_COLOR_CORRECTION_TRANSFORM, + CameraMetadataNative.getTag("android.colorCorrection.transform")); + assertEquals(ANDROID_CONTROL_AE_ANTIBANDING_MODE, + CameraMetadataNative.getTag("android.control.aeAntibandingMode")); + assertEquals(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, + CameraMetadataNative.getTag("android.control.aeExposureCompensation")); + + // Test failures + + try { + CameraMetadataNative.getTag(null); + fail("A null key should throw NPE"); + } catch(NullPointerException e) { + } + + try { + CameraMetadataNative.getTag("android.control"); + fail("A section name only should not be a valid key"); + } catch(IllegalArgumentException e) { + } + + try { + CameraMetadataNative.getTag("android.control.thisTagNameIsFakeAndDoesNotExist"); + fail("A valid section with an invalid tag name should not be a valid key"); + } catch(IllegalArgumentException e) { + } + + try { + CameraMetadataNative.getTag("android"); + fail("A namespace name only should not be a valid key"); + } catch(IllegalArgumentException e) { + } + + try { + CameraMetadataNative.getTag("this.key.is.definitely.invalid"); + fail("A completely fake key name should not be valid"); + } catch(IllegalArgumentException e) { + } + } + + @SmallTest + public void testGetTypeFromTag() { + assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE)); + assertEquals(TYPE_RATIONAL, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); + assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS)); + assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE)); + assertEquals(TYPE_INT32, + CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION)); + + try { + CameraMetadataNative.getNativeType(0xDEADF00D); + fail("No type should exist for invalid tag 0xDEADF00D"); + } catch(IllegalArgumentException e) { + } + } + + @SmallTest + public void testReadWriteValues() { + final byte ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; + byte[] valueResult; + + assertEquals(0, mMetadata.getEntryCount()); + assertEquals(true, mMetadata.isEmpty()); + + // + // android.colorCorrection.mode (single enum byte) + // + + assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE)); + + // Write/read null values + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, null); + assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE)); + + // Write 0 values + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] {}); + + // Read 0 values + valueResult = mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE); + assertNotNull(valueResult); + assertEquals(0, valueResult.length); + + assertEquals(1, mMetadata.getEntryCount()); + assertEquals(false, mMetadata.isEmpty()); + + // Write 1 value + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] { + ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY + }); + + // Read 1 value + valueResult = mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE); + assertNotNull(valueResult); + assertEquals(1, valueResult.length); + assertEquals(ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY, valueResult[0]); + + assertEquals(1, mMetadata.getEntryCount()); + assertEquals(false, mMetadata.isEmpty()); + + // + // android.colorCorrection.colorCorrectionGains (float x 4 array) + // + + final float[] colorCorrectionGains = new float[] { 1.0f, 2.0f, 3.0f, 4.0f}; + byte[] colorCorrectionGainsAsByteArray = new byte[colorCorrectionGains.length * 4]; + ByteBuffer colorCorrectionGainsByteBuffer = + ByteBuffer.wrap(colorCorrectionGainsAsByteArray).order(ByteOrder.nativeOrder()); + for (float f : colorCorrectionGains) + colorCorrectionGainsByteBuffer.putFloat(f); + + // Read + assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, colorCorrectionGainsAsByteArray); + + // Write + assertArrayEquals(colorCorrectionGainsAsByteArray, + mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); + + assertEquals(2, mMetadata.getEntryCount()); + assertEquals(false, mMetadata.isEmpty()); + + // Erase + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, null); + assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); + assertEquals(1, mMetadata.getEntryCount()); + } + + private static void assertArrayEquals(T expected, T actual) { + assertEquals(Array.getLength(expected), Array.getLength(actual)); + + int len = Array.getLength(expected); + for (int i = 0; i < len; ++i) { + assertEquals(Array.get(expected, i), Array.get(actual, i)); + } + } + + private void checkKeyGetAndSet(String keyStr, Class type, T value) { + assertFalse("Use checkKeyGetAndSetArray to compare array Keys", type.isArray()); + + Key key = new Key(keyStr, type); + assertNull(mMetadata.get(key)); + mMetadata.set(key, null); + assertNull(mMetadata.get(key)); + mMetadata.set(key, value); + + T actual = mMetadata.get(key); + assertEquals(value, actual); + } + + private void checkKeyGetAndSetArray(String keyStr, Class type, T value) { + assertTrue(type.isArray()); + + Key key = new Key(keyStr, type); + assertNull(mMetadata.get(key)); + mMetadata.set(key, value); + assertArrayEquals(value, mMetadata.get(key)); + } + + @SmallTest + public void testReadWritePrimitive() { + // int32 (single) + checkKeyGetAndSet("android.control.aeExposureCompensation", Integer.TYPE, 0xC0FFEE); + + // byte (single) + checkKeyGetAndSet("android.flash.maxEnergy", Byte.TYPE, (byte)6); + + // int64 (single) + checkKeyGetAndSet("android.flash.firingTime", Long.TYPE, 0xABCD12345678FFFFL); + + // float (single) + checkKeyGetAndSet("android.lens.aperture", Float.TYPE, Float.MAX_VALUE); + + // double (single) -- technically double x 3, but we fake it + checkKeyGetAndSet("android.jpeg.gpsCoordinates", Double.TYPE, Double.MAX_VALUE); + + // rational (single) + checkKeyGetAndSet("android.sensor.baseGainFactor", Rational.class, new Rational(1, 2)); + + /** + * Weirder cases, that don't map 1:1 with the native types + */ + + // bool (single) -- with TYPE_BYTE + checkKeyGetAndSet("android.control.aeLock", Boolean.TYPE, true); + + // integer (single) -- with TYPE_BYTE + checkKeyGetAndSet("android.control.aePrecaptureTrigger", Integer.TYPE, 6); + } + + @SmallTest + public void testReadWritePrimitiveArray() { + // int32 (n) + checkKeyGetAndSetArray("android.sensor.info.sensitivityRange", int[].class, + new int[] { + 0xC0FFEE, 0xDEADF00D + }); + + // byte (n) + checkKeyGetAndSetArray("android.statistics.faceScores", byte[].class, new byte[] { + 1, 2, 3, 4 + }); + + // int64 (n) + checkKeyGetAndSetArray("android.scaler.availableProcessedMinDurations", long[].class, + new long[] { + 0xABCD12345678FFFFL, 0x1234ABCD5678FFFFL, 0xFFFF12345678ABCDL + }); + + // float (n) + checkKeyGetAndSetArray("android.lens.info.availableApertures", float[].class, + new float[] { + Float.MAX_VALUE, Float.MIN_NORMAL, Float.MIN_VALUE + }); + + // double (n) -- in particular double x 3 + checkKeyGetAndSetArray("android.jpeg.gpsCoordinates", double[].class, + new double[] { + Double.MAX_VALUE, Double.MIN_NORMAL, Double.MIN_VALUE + }); + + // rational (n) -- in particular rational x 9 + checkKeyGetAndSetArray("android.sensor.calibrationTransform1", Rational[].class, + new Rational[] { + new Rational(1, 2), new Rational(3, 4), new Rational(5, 6), + new Rational(7, 8), new Rational(9, 10), new Rational(10, 11), + new Rational(12, 13), new Rational(14, 15), new Rational(15, 16) + }); + + /** + * Weirder cases, that don't map 1:1 with the native types + */ + + // bool (n) -- with TYPE_BYTE + checkKeyGetAndSetArray("android.control.aeLock", boolean[].class, new boolean[] { + true, false, true + }); + + + // integer (n) -- with TYPE_BYTE + checkKeyGetAndSetArray("android.control.aeAvailableModes", int[].class, new int[] { + 1, 2, 3, 4 + }); + } + + private enum ColorCorrectionMode { + TRANSFORM_MATRIX, + FAST, + HIGH_QUALITY + } + + private enum AeAntibandingMode { + OFF, + _50HZ, + _60HZ, + AUTO + } + + // TODO: special values for the enum. + private enum AvailableFormat { + RAW_SENSOR, + YV12, + YCrCb_420_SP, + IMPLEMENTATION_DEFINED, + YCbCr_420_888, + BLOB + } + + @SmallTest + public void testReadWriteEnum() { + // byte (single) + checkKeyGetAndSet("android.colorCorrection.mode", ColorCorrectionMode.class, + ColorCorrectionMode.HIGH_QUALITY); + + // byte (single) + checkKeyGetAndSet("android.control.aeAntibandingMode", AeAntibandingMode.class, + AeAntibandingMode.AUTO); + + // byte (n) + checkKeyGetAndSetArray("android.control.aeAvailableAntibandingModes", + AeAntibandingMode[].class, new AeAntibandingMode[] { + AeAntibandingMode.OFF, AeAntibandingMode._50HZ, AeAntibandingMode._60HZ, + AeAntibandingMode.AUTO + }); + + /** + * Stranger cases that don't use byte enums + */ + // int (n) + checkKeyGetAndSetArray("android.scaler.availableFormats", AvailableFormat[].class, + new AvailableFormat[] { + AvailableFormat.RAW_SENSOR, + AvailableFormat.YV12, + AvailableFormat.IMPLEMENTATION_DEFINED, + AvailableFormat.YCbCr_420_888, + AvailableFormat.BLOB + }); + + } + + @SmallTest + public void testReadWriteEnumWithCustomValues() { + CameraMetadataNative.registerEnumValues(AeAntibandingMode.class, new int[] { + 0, + 10, + 20, + 30 + }); + + // byte (single) + checkKeyGetAndSet("android.control.aeAntibandingMode", AeAntibandingMode.class, + AeAntibandingMode.AUTO); + + // byte (n) + checkKeyGetAndSetArray("android.control.aeAvailableAntibandingModes", + AeAntibandingMode[].class, new AeAntibandingMode[] { + AeAntibandingMode.OFF, AeAntibandingMode._50HZ, AeAntibandingMode._60HZ, + AeAntibandingMode.AUTO + }); + + Key aeAntibandingModeKey = + new Key("android.control.aeAvailableAntibandingModes", + AeAntibandingMode[].class); + byte[] aeAntibandingModeValues = mMetadata.readValues(CameraMetadataNative + .getTag("android.control.aeAvailableAntibandingModes")); + byte[] expectedValues = new byte[] { 0, 10, 20, 30 }; + assertArrayEquals(expectedValues, aeAntibandingModeValues); + + + /** + * Stranger cases that don't use byte enums + */ + // int (n) + CameraMetadataNative.registerEnumValues(AvailableFormat.class, new int[] { + 0x20, + 0x32315659, + 0x11, + 0x22, + 0x23, + 0x21, + }); + + checkKeyGetAndSetArray("android.scaler.availableFormats", AvailableFormat[].class, + new AvailableFormat[] { + AvailableFormat.RAW_SENSOR, + AvailableFormat.YV12, + AvailableFormat.IMPLEMENTATION_DEFINED, + AvailableFormat.YCbCr_420_888, + AvailableFormat.BLOB + }); + + Key availableFormatsKey = + new Key("android.scaler.availableFormats", + AvailableFormat[].class); + byte[] availableFormatValues = mMetadata.readValues(CameraMetadataNative + .getTag(availableFormatsKey.getName())); + + int[] expectedIntValues = new int[] { + 0x20, + 0x32315659, + 0x22, + 0x23, + 0x21 + }; + + ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); + + assertEquals(expectedIntValues.length * 4, availableFormatValues.length); + for (int i = 0; i < expectedIntValues.length; ++i) { + assertEquals(expectedIntValues[i], bf.getInt()); + } + } + + @SmallTest + public void testReadWriteSize() { + // int32 x n + checkKeyGetAndSet("android.jpeg.thumbnailSize", Size.class, new Size(123, 456)); + + // int32 x 2 x n + checkKeyGetAndSetArray("android.scaler.availableJpegSizes", Size[].class, new Size[] { + new Size(123, 456), + new Size(0xDEAD, 0xF00D), + new Size(0xF00, 0xB00) + }); + } + + @SmallTest + public void testReadWriteRectangle() { + // int32 x n + checkKeyGetAndSet("android.scaler.cropRegion", Rect.class, new Rect(10, 11, 1280, 1024)); + + // int32 x 2 x n + checkKeyGetAndSetArray("android.statistics.faceRectangles", Rect[].class, new Rect[] { + new Rect(110, 111, 11280, 11024), + new Rect(210, 111, 21280, 21024), + new Rect(310, 111, 31280, 31024) + }); + } + + @SmallTest + public void testReadWriteString() { + // (byte) string + Key gpsProcessingMethodKey = + new Key("android.jpeg.gpsProcessingMethod", String.class); + + String helloWorld = new String("HelloWorld"); + byte[] helloWorldBytes = new byte[] { + 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0' }; + + mMetadata.set(gpsProcessingMethodKey, helloWorld); + + String actual = mMetadata.get(gpsProcessingMethodKey); + assertEquals(helloWorld, actual); + + byte[] actualBytes = mMetadata.readValues(getTag(gpsProcessingMethodKey.getName())); + assertArrayEquals(helloWorldBytes, actualBytes); + + // Does not yet test as a string[] since we don't support that in native code. + + // (byte) string + Key gpsProcessingMethodKeyArray = + new Key("android.jpeg.gpsProcessingMethod", String[].class); + + String[] gpsStrings = new String[] { "HelloWorld", "FooBar", "Shazbot" }; + byte[] gpsBytes = new byte[] { + 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0', + 'F', 'o', 'o', 'B', 'a', 'r', '\0', + 'S', 'h', 'a', 'z', 'b', 'o', 't', '\0'}; + + mMetadata.set(gpsProcessingMethodKeyArray, gpsStrings); + + String[] actualArray = mMetadata.get(gpsProcessingMethodKeyArray); + assertArrayEquals(gpsStrings, actualArray); + + byte[] actualBytes2 = mMetadata.readValues(getTag(gpsProcessingMethodKeyArray.getName())); + assertArrayEquals(gpsBytes, actualBytes2); + } + + void compareGeneric(T expected, T actual) { + assertEquals(expected, actual); + } + + @SmallTest + public void testReadWriteOverride() { + // + // android.scaler.availableFormats (int x n array) + // + int[] availableFormats = new int[] { + 0x20, // RAW_SENSOR + 0x32315659, // YV12 + 0x11, // YCrCb_420_SP + 0x100, // ImageFormat.JPEG + 0x22, // IMPLEMENTATION_DEFINED + 0x23, // YCbCr_420_888 + }; + int[] expectedIntValues = new int[] { + 0x20, // RAW_SENSOR + 0x32315659, // YV12 + 0x11, // YCrCb_420_SP + 0x21, // BLOB + 0x22, // IMPLEMENTATION_DEFINED + 0x23, // YCbCr_420_888 + }; + int availableFormatTag = CameraMetadataNative.getTag("android.scaler.availableFormats"); + + // Write + mMetadata.set(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, availableFormats); + + byte[] availableFormatValues = mMetadata.readValues(availableFormatTag); + + ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); + + assertEquals(expectedIntValues.length * 4, availableFormatValues.length); + for (int i = 0; i < expectedIntValues.length; ++i) { + assertEquals(expectedIntValues[i], bf.getInt()); + } + // Read + byte[] availableFormatsAsByteArray = new byte[expectedIntValues.length * 4]; + ByteBuffer availableFormatsByteBuffer = + ByteBuffer.wrap(availableFormatsAsByteArray).order(ByteOrder.nativeOrder()); + for (int value : expectedIntValues) { + availableFormatsByteBuffer.putInt(value); + } + mMetadata.writeValues(availableFormatTag, availableFormatsAsByteArray); + + int[] resultFormats = mMetadata.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); + assertNotNull("result available formats shouldn't be null", resultFormats); + assertArrayEquals(availableFormats, resultFormats); + + // + // android.statistics.faces (Face x n array) + // + int[] expectedFaceIds = new int[] {1, 2, 3, 4, 5}; + byte[] expectedFaceScores = new byte[] {10, 20, 30, 40, 50}; + int numFaces = expectedFaceIds.length; + Rect[] expectedRects = new Rect[numFaces]; + for (int i = 0; i < numFaces; i++) { + expectedRects[i] = new Rect(i*4 + 1, i * 4 + 2, i * 4 + 3, i * 4 + 4); + } + int[] expectedFaceLM = new int[] { + 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, + }; + Point[] expectedFaceLMPoints = new Point[numFaces * 3]; + for (int i = 0; i < numFaces; i++) { + expectedFaceLMPoints[i*3] = new Point(expectedFaceLM[i*6], expectedFaceLM[i*6+1]); + expectedFaceLMPoints[i*3+1] = new Point(expectedFaceLM[i*6+2], expectedFaceLM[i*6+3]); + expectedFaceLMPoints[i*3+2] = new Point(expectedFaceLM[i*6+4], expectedFaceLM[i*6+5]); + } + + /** + * Read - FACE_DETECT_MODE == FULL + */ + mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, + CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL); + mMetadata.set(CaptureResult.STATISTICS_FACE_IDS, expectedFaceIds); + mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); + mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); + mMetadata.set(CaptureResult.STATISTICS_FACE_LANDMARKS, expectedFaceLM); + Face[] resultFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); + assertEquals(numFaces, resultFaces.length); + for (int i = 0; i < numFaces; i++) { + assertEquals(expectedFaceIds[i], resultFaces[i].getId()); + assertEquals(expectedFaceScores[i], resultFaces[i].getScore()); + assertEquals(expectedRects[i], resultFaces[i].getBounds()); + assertEquals(expectedFaceLMPoints[i*3], resultFaces[i].getLeftEyePosition()); + assertEquals(expectedFaceLMPoints[i*3+1], resultFaces[i].getRightEyePosition()); + assertEquals(expectedFaceLMPoints[i*3+2], resultFaces[i].getMouthPosition()); + } + + /** + * Read - FACE_DETECT_MODE == SIMPLE + */ + mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, + CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE); + mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); + mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); + Face[] resultSimpleFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); + assertEquals(numFaces, resultSimpleFaces.length); + for (int i = 0; i < numFaces; i++) { + assertEquals(Face.ID_UNSUPPORTED, resultSimpleFaces[i].getId()); + assertEquals(expectedFaceScores[i], resultSimpleFaces[i].getScore()); + assertEquals(expectedRects[i], resultSimpleFaces[i].getBounds()); + assertNull(resultSimpleFaces[i].getLeftEyePosition()); + assertNull(resultSimpleFaces[i].getRightEyePosition()); + assertNull(resultSimpleFaces[i].getMouthPosition()); + } + + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..727af781bd043d70cf2442e3b3716391c76af503 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java @@ -0,0 +1,172 @@ +/* + * 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.mediaframeworktest.unit; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.utils.CameraBinderDecorator; +import android.hardware.camera2.utils.CameraRuntimeException; +import android.os.DeadObjectException; +import android.os.RemoteException; +import android.os.TransactionTooLargeException; +import android.test.suitebuilder.annotation.SmallTest; + +import static org.mockito.Mockito.*; +import static android.hardware.camera2.utils.CameraBinderDecorator.*; +import static android.hardware.camera2.CameraAccessException.*; + +import junit.framework.Assert; + +public class CameraUtilsBinderDecoratorTest extends junit.framework.TestCase { + + private interface ICameraBinderStereotype { + + double doNothing(); + + // int is a 'status_t' + int doSomethingPositive(); + + int doSomethingNoError(); + + int doSomethingPermissionDenied(); + + int doSomethingAlreadyExists(); + + int doSomethingBadValue(); + + int doSomethingDeadObject() throws CameraRuntimeException; + + int doSomethingBadPolicy() throws CameraRuntimeException; + + int doSomethingDeviceBusy() throws CameraRuntimeException; + + int doSomethingNoSuchDevice() throws CameraRuntimeException; + + int doSomethingUnknownErrorCode(); + + int doSomethingThrowDeadObjectException() throws RemoteException; + + int doSomethingThrowTransactionTooLargeException() throws RemoteException; + } + + private static final double SOME_ARBITRARY_DOUBLE = 1.0; + private static final int SOME_ARBITRARY_POSITIVE_INT = 5; + private static final int SOME_ARBITRARY_NEGATIVE_INT = -0xC0FFEE; + + @SmallTest + public void testStereotypes() { + + ICameraBinderStereotype mock = mock(ICameraBinderStereotype.class); + try { + when(mock.doNothing()).thenReturn(SOME_ARBITRARY_DOUBLE); + when(mock.doSomethingPositive()).thenReturn(SOME_ARBITRARY_POSITIVE_INT); + when(mock.doSomethingNoError()).thenReturn(NO_ERROR); + when(mock.doSomethingPermissionDenied()).thenReturn(PERMISSION_DENIED); + when(mock.doSomethingAlreadyExists()).thenReturn(ALREADY_EXISTS); + when(mock.doSomethingBadValue()).thenReturn(BAD_VALUE); + when(mock.doSomethingDeadObject()).thenReturn(DEAD_OBJECT); + when(mock.doSomethingBadPolicy()).thenReturn(EACCES); + when(mock.doSomethingDeviceBusy()).thenReturn(EBUSY); + when(mock.doSomethingNoSuchDevice()).thenReturn(ENODEV); + when(mock.doSomethingUnknownErrorCode()).thenReturn(SOME_ARBITRARY_NEGATIVE_INT); + when(mock.doSomethingThrowDeadObjectException()).thenThrow(new DeadObjectException()); + when(mock.doSomethingThrowTransactionTooLargeException()).thenThrow( + new TransactionTooLargeException()); + } catch (RemoteException e) { + Assert.fail("Unreachable"); + } + + ICameraBinderStereotype decoratedMock = CameraBinderDecorator.newInstance(mock); + + // ignored by decorator because return type is double, not int + assertEquals(SOME_ARBITRARY_DOUBLE, decoratedMock.doNothing()); + + // pass through for positive values + assertEquals(SOME_ARBITRARY_POSITIVE_INT, decoratedMock.doSomethingPositive()); + + // pass through NO_ERROR + assertEquals(NO_ERROR, decoratedMock.doSomethingNoError()); + + try { + decoratedMock.doSomethingPermissionDenied(); + Assert.fail("Should've thrown SecurityException"); + } catch (SecurityException e) { + } + + assertEquals(ALREADY_EXISTS, decoratedMock.doSomethingAlreadyExists()); + + try { + decoratedMock.doSomethingBadValue(); + Assert.fail("Should've thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + } + + try { + decoratedMock.doSomethingDeadObject(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISCONNECTED, e.getReason()); + } + + try { + decoratedMock.doSomethingBadPolicy(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISABLED, e.getReason()); + } + + try { + decoratedMock.doSomethingDeviceBusy(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_IN_USE, e.getReason()); + } + + try { + decoratedMock.doSomethingNoSuchDevice(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISCONNECTED, e.getReason()); + } + + try { + decoratedMock.doSomethingUnknownErrorCode(); + Assert.fail("Should've thrown UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + assertEquals(String.format("Unknown error %d", + SOME_ARBITRARY_NEGATIVE_INT), e.getMessage()); + } + + try { + decoratedMock.doSomethingThrowDeadObjectException(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISCONNECTED, e.getReason()); + } catch (RemoteException e) { + Assert.fail("Should not throw a DeadObjectException directly, but rethrow"); + } + + try { + decoratedMock.doSomethingThrowTransactionTooLargeException(); + Assert.fail("Should've thrown UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + assertTrue(e.getCause() instanceof TransactionTooLargeException); + } catch (RemoteException e) { + Assert.fail("Should not throw a TransactionTooLargeException directly, but rethrow"); + } + } + +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c3b60065916cbbaa83a335e09fced37c132d6391 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java @@ -0,0 +1,171 @@ +/* + * 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.mediaframeworktest.unit; + +import android.test.suitebuilder.annotation.SmallTest; +import android.hardware.camera2.utils.*; +import android.hardware.camera2.utils.Decorator.DecoratorListener; + +import junit.framework.Assert; + +import java.lang.reflect.Method; + +/** + * adb shell am instrument -e class 'com.android.mediaframeworktest.unit.CameraUtilsDecoratorTest' \ + * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner + */ +public class CameraUtilsDecoratorTest extends junit.framework.TestCase { + private DummyListener mDummyListener; + private DummyInterface mIface; + + @Override + public void setUp() { + mDummyListener = new DummyListener(); + mIface = Decorator.newInstance(new DummyImpl(), mDummyListener); + } + + interface DummyInterface { + int addValues(int x, int y, int z); + + void raiseException() throws Exception; + + void raiseUnsupportedOperationException() throws UnsupportedOperationException; + } + + class DummyImpl implements DummyInterface { + @Override + public int addValues(int x, int y, int z) { + return x + y + z; + } + + @Override + public void raiseException() throws Exception { + throw new Exception("Test exception"); + } + + @Override + public void raiseUnsupportedOperationException() throws UnsupportedOperationException { + throw new UnsupportedOperationException("Test exception"); + } + } + + class DummyListener implements DecoratorListener { + + public boolean beforeCalled = false; + public boolean afterCalled = false; + public boolean catchCalled = false; + public boolean finallyCalled = false; + public Object resultValue = null; + + public boolean raiseException = false; + + @Override + public void onBeforeInvocation(Method m, Object[] args) { + beforeCalled = true; + } + + @Override + public void onAfterInvocation(Method m, Object[] args, Object result) { + afterCalled = true; + resultValue = result; + + if (raiseException) { + throw new UnsupportedOperationException("Test exception"); + } + } + + @Override + public boolean onCatchException(Method m, Object[] args, Throwable t) { + catchCalled = true; + return false; + } + + @Override + public void onFinally(Method m, Object[] args) { + finallyCalled = true; + } + + }; + + @SmallTest + public void testDecorator() { + + // TODO rewrite this using mocks + + assertTrue(mIface.addValues(1, 2, 3) == 6); + assertTrue(mDummyListener.beforeCalled); + assertTrue(mDummyListener.afterCalled); + + int resultValue = (Integer)mDummyListener.resultValue; + assertTrue(resultValue == 6); + assertTrue(mDummyListener.finallyCalled); + assertFalse(mDummyListener.catchCalled); + } + + @SmallTest + public void testDecoratorExceptions() { + + boolean gotExceptions = false; + try { + mIface.raiseException(); + } catch (Exception e) { + gotExceptions = true; + assertTrue(e.getMessage() == "Test exception"); + } + assertTrue(gotExceptions); + assertTrue(mDummyListener.beforeCalled); + assertFalse(mDummyListener.afterCalled); + assertTrue(mDummyListener.catchCalled); + assertTrue(mDummyListener.finallyCalled); + } + + @SmallTest + public void testDecoratorUnsupportedOperationException() { + + boolean gotExceptions = false; + try { + mIface.raiseUnsupportedOperationException(); + } catch (UnsupportedOperationException e) { + gotExceptions = true; + assertTrue(e.getMessage() == "Test exception"); + } + assertTrue(gotExceptions); + assertTrue(mDummyListener.beforeCalled); + assertFalse(mDummyListener.afterCalled); + assertTrue(mDummyListener.catchCalled); + assertTrue(mDummyListener.finallyCalled); + } + + @SmallTest + public void testDecoratorRaisesException() { + + boolean gotExceptions = false; + try { + mDummyListener.raiseException = true; + mIface.addValues(1, 2, 3); + Assert.fail("unreachable"); + } catch (UnsupportedOperationException e) { + gotExceptions = true; + assertTrue(e.getMessage() == "Test exception"); + } + assertTrue(gotExceptions); + assertTrue(mDummyListener.beforeCalled); + assertTrue(mDummyListener.afterCalled); + assertFalse(mDummyListener.catchCalled); + assertTrue(mDummyListener.finallyCalled); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..02c9f2af11ad7cfaa47262ae242dfcaa80a63571 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java @@ -0,0 +1,77 @@ +/* + * 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.mediaframeworktest.unit; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.utils.CameraRuntimeException; +import android.hardware.camera2.utils.UncheckedThrow; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.Assert; + +public class CameraUtilsRuntimeExceptionTest extends junit.framework.TestCase { + + @SmallTest + public void testCameraRuntimeException1() { + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + } + + @SmallTest + public void testCameraRuntimeException2() { + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello"); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertEquals("Hello", e.getMessage()); + assertNull(e.getCause()); + } + } + + @SmallTest + public void testCameraRuntimeException3() { + Throwable cause = new IllegalStateException("For great justice"); + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, cause); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertNull(e.getMessage()); + assertEquals(cause, e.getCause()); + } + } + + @SmallTest + public void testCameraRuntimeException4() { + Throwable cause = new IllegalStateException("For great justice"); + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello", cause); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertEquals("Hello", e.getMessage()); + assertEquals(cause, e.getCause()); + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b648763db9e94a8ad7447d2cb5d9c38347fb02e0 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java @@ -0,0 +1,40 @@ +/* + * 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.mediaframeworktest.unit; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.utils.UncheckedThrow; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.Assert; + +public class CameraUtilsUncheckedThrowTest extends junit.framework.TestCase { + + private void fakeNeverThrowsCameraAccess() throws CameraAccessException { + } + + @SmallTest + public void testUncheckedThrow() { + try { + UncheckedThrow.throwAnyException(new CameraAccessException( + CameraAccessException.CAMERA_DISCONNECTED)); + Assert.fail("unreachable"); + fakeNeverThrowsCameraAccess(); + } catch (CameraAccessException e) { + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f6cd9901cd39e44dff40604f0be0c9c1bd872711 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java @@ -0,0 +1,181 @@ +/* + * 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.mediaframeworktest.unit; + +import static org.mockito.Mockito.*; + +import android.graphics.ImageFormat; +import android.media.Image; +import android.media.Image.Plane; +import android.media.ImageReader; +import android.media.ImageReader.OnImageAvailableListener; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class ImageReaderTest extends AndroidTestCase { + + private static final String TAG = "ImageReaderTest-unit"; + + private static final int DEFAULT_WIDTH = 640; + private static final int DEFAULT_HEIGHT = 480; + private static final int DEFAULT_FORMAT = ImageFormat.YUV_420_888; + private static final int DEFAULT_MAX_IMAGES = 3; + + private ImageReader mReader; + private Image mImage1; + private Image mImage2; + private Image mImage3; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + /** + * Workaround for mockito and JB-MR2 incompatibility + * + * Avoid java.lang.IllegalArgumentException: dexcache == null + * https://code.google.com/p/dexmaker/issues/detail?id=2 + */ + System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); + + // TODO: refactor above into one of the test runners + + mReader = spy(ImageReader.newInstance(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + DEFAULT_MAX_IMAGES)); + mImage1 = mock(Image.class); + mImage2 = mock(Image.class); + mImage3 = mock(Image.class); + + /** + * Ensure rest of classes are mockable + */ + { + mock(Plane.class); + mock(OnImageAvailableListener.class); + } + + } + + @Override + protected void tearDown() throws Exception { + mReader.close(); + + super.tearDown(); + } + + /** + * Return null when there is nothing in the image queue. + */ + @SmallTest + public void testGetLatestImageEmpty() { + when(mReader.acquireNextImage()).thenReturn(null); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(null); + assertEquals(null, mReader.acquireLatestImage()); + } + + /** + * Return the last image from the image queue, close up the rest. + */ + @SmallTest + public void testGetLatestImage1() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(null); + assertEquals(mImage1, mReader.acquireLatestImage()); + verify(mImage1, never()).close(); + } + + /** + * Return the last image from the image queue, close up the rest. + */ + @SmallTest + public void testGetLatestImage2() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).thenReturn(null); + assertEquals(mImage2, mReader.acquireLatestImage()); + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, never()).close(); + } + + /** + * Return the last image from the image queue, close up the rest. + */ + @SmallTest + public void testGetLatestImage3() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2). + thenReturn(mImage3). + thenReturn(null); + assertEquals(mImage3, mReader.acquireLatestImage()); + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, atLeastOnce()).close(); + verify(mImage3, never()).close(); + } + + /** + * Return null if get a IllegalStateException with no images in the queue. + */ + @SmallTest + public void testGetLatestImageTooManyBuffersAcquiredEmpty() { + when(mReader.acquireNextImage()).thenThrow(new IllegalStateException()); + try { + mReader.acquireLatestImage(); + fail("Expected IllegalStateException to be thrown"); + } catch(IllegalStateException e) { + } + } + + /** + * All images are cleaned up when we get an unexpected Error. + */ + @SmallTest + public void testGetLatestImageExceptionalError() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2). + thenReturn(mImage3). + thenThrow(new OutOfMemoryError()); + try { + mReader.acquireLatestImage(); + fail("Impossible"); + } catch(OutOfMemoryError e) { + } + + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, atLeastOnce()).close(); + verify(mImage3, atLeastOnce()).close(); + } + + /** + * All images are cleaned up when we get an unexpected RuntimeException. + */ + @SmallTest + public void testGetLatestImageExceptionalRuntime() { + + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2). + thenReturn(mImage3). + thenThrow(new RuntimeException()); + try { + mReader.acquireLatestImage(); + fail("Impossible"); + } catch(RuntimeException e) { + } + + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, atLeastOnce()).close(); + verify(mImage3, atLeastOnce()).close(); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9621f92b78f53d51d352cf8d26f7d84a99dd244b --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java @@ -0,0 +1,151 @@ +/* + * 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.mediaframeworktest.unit; + +import android.test.suitebuilder.annotation.SmallTest; +import android.hardware.camera2.Rational; + +/** + *
        + * adb shell am instrument \
        + *      -e class 'com.android.mediaframeworktest.unit.RationalTest' \
        + *      -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
        + * 
        + */ +public class RationalTest extends junit.framework.TestCase { + @SmallTest + public void testConstructor() { + + // Simple case + Rational r = new Rational(1, 2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Denominator negative + r = new Rational(-1, 2); + assertEquals(-1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Numerator negative + r = new Rational(1, -2); + assertEquals(-1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Both negative + r = new Rational(-1, -2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Infinity. + r = new Rational(1, 0); + assertEquals(0, r.getNumerator()); + assertEquals(0, r.getDenominator()); + + // Negative infinity. + r = new Rational(-1, 0); + assertEquals(0, r.getNumerator()); + assertEquals(0, r.getDenominator()); + + // NaN. + r = new Rational(0, 0); + assertEquals(0, r.getNumerator()); + assertEquals(0, r.getDenominator()); + } + + @SmallTest + public void testGcd() { + Rational r = new Rational(1, 2); + assertEquals(1, r.gcd()); + + Rational twoThirds = new Rational(2, 3); + assertEquals(1, twoThirds.gcd()); + + Rational moreComplicated2 = new Rational(5*78, 7*78); + assertEquals(78, moreComplicated2.gcd()); + + Rational oneHalf = new Rational(-1, 2); + assertEquals(1, oneHalf.gcd()); + + twoThirds = new Rational(-2, 3); + assertEquals(1, twoThirds.gcd()); + } + + @SmallTest + public void testEquals() { + Rational r = new Rational(1, 2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + assertEquals(r, r); + assertFalse(r.equals(null)); + assertFalse(r.equals(new Object())); + + Rational twoThirds = new Rational(2, 3); + assertFalse(r.equals(twoThirds)); + assertFalse(twoThirds.equals(r)); + + Rational fourSixths = new Rational(4, 6); + assertEquals(twoThirds, fourSixths); + assertEquals(fourSixths, twoThirds); + + Rational moreComplicated = new Rational(5*6*7*8*9, 1*2*3*4*5); + Rational moreComplicated2 = new Rational(5*6*7*8*9*78, 1*2*3*4*5*78); + assertEquals(moreComplicated, moreComplicated2); + assertEquals(moreComplicated2, moreComplicated); + + // Ensure negatives are fine + twoThirds = new Rational(-2, 3); + fourSixths = new Rational(-4, 6); + assertEquals(twoThirds, fourSixths); + assertEquals(fourSixths, twoThirds); + + moreComplicated = new Rational(-5*6*7*8*9, 1*2*3*4*5); + moreComplicated2 = new Rational(-5*6*7*8*9*78, 1*2*3*4*5*78); + assertEquals(moreComplicated, moreComplicated2); + assertEquals(moreComplicated2, moreComplicated); + + Rational nan = new Rational(0, 0); + Rational nan2 = new Rational(0, 0); + assertTrue(nan.equals(nan)); + assertTrue(nan.equals(nan2)); + assertTrue(nan2.equals(nan)); + assertFalse(nan.equals(r)); + assertFalse(r.equals(nan)); + + // Infinities of the same sign are equal. + Rational posInf = new Rational(1, 0); + Rational posInf2 = new Rational(2, 0); + Rational negInf = new Rational(-1, 0); + Rational negInf2 = new Rational(-2, 0); + assertEquals(posInf, posInf); + assertEquals(negInf, negInf); + assertEquals(posInf, posInf2); + assertEquals(negInf, negInf2); + + // Infinities aren't equal to anything else. + assertFalse(posInf.equals(negInf)); + assertFalse(negInf.equals(posInf)); + assertFalse(negInf.equals(r)); + assertFalse(posInf.equals(r)); + assertFalse(r.equals(negInf)); + assertFalse(r.equals(posInf)); + assertFalse(posInf.equals(nan)); + assertFalse(negInf.equals(nan)); + assertFalse(nan.equals(posInf)); + assertFalse(nan.equals(negInf)); + } +} diff --git a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java index fe3929d16c76f6b7cbffcee6c93dd766d7605b9c..0304640345169dd99acfbb13343842a20a91bfd3 100644 --- a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java +++ b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java @@ -429,7 +429,7 @@ public class ScoAudioTest extends Activity { mMediaRecorder.start(); mState = 1; } catch (Exception e) { - Log.e(TAG, "Could start MediaRecorder: " + e.toString()); + Log.e(TAG, "Could start MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; mState = 0; @@ -439,7 +439,7 @@ public class ScoAudioTest extends Activity { mMediaRecorder.stop(); mMediaRecorder.reset(); } catch (Exception e) { - Log.e(TAG, "Could not stop MediaRecorder: " + e.toString()); + Log.e(TAG, "Could not stop MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; } finally { @@ -466,7 +466,7 @@ public class ScoAudioTest extends Activity { mMediaRecorder.prepare(); } catch (Exception e) { - Log.e(TAG, "Could not prepare MediaRecorder: " + e.toString()); + Log.e(TAG, "Could not prepare MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; } @@ -475,9 +475,14 @@ public class ScoAudioTest extends Activity { @Override public void stop() { if (mMediaRecorder != null) { - mMediaRecorder.stop(); - mMediaRecorder.release(); - mMediaRecorder = null; + try { + mMediaRecorder.stop(); + } catch (Exception e) { + Log.e(TAG, "Could not stop MediaRecorder: ", e); + } finally { + mMediaRecorder.release(); + mMediaRecorder = null; + } } updatePlayPauseButton(); } diff --git a/media/tests/SoundPoolTest/AndroidManifest.xml b/media/tests/SoundPoolTest/AndroidManifest.xml index 126276c04d5e4a96249701e72e54ee8241fd0e24..8a29052ba50b5cede107bfa45d4d90954b59772e 100644 --- a/media/tests/SoundPoolTest/AndroidManifest.xml +++ b/media/tests/SoundPoolTest/AndroidManifest.xml @@ -8,4 +8,5 @@ package="com.android.soundpooltest"> + diff --git a/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java b/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java index 33db2ddb900fe01f58a3b3011ac8fafc424b2fbf..cc3306c79d3f592786477450b6622f3ae5f0b458 100644 --- a/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java +++ b/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java @@ -143,7 +143,7 @@ public class SoundPoolTest extends Activity if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); sleep(50); } - if (DEBUG) Log.d(LOG_TAG, "End scale test"); + if (DEBUG) Log.d(LOG_TAG, "End sounds test"); return true; } @@ -165,7 +165,7 @@ public class SoundPoolTest extends Activity if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); sleep(50); } - if (DEBUG) Log.d(LOG_TAG, "End sounds test"); + if (DEBUG) Log.d(LOG_TAG, "End scale test"); return true; } @@ -189,6 +189,7 @@ public class SoundPoolTest extends Activity if (DEBUG) Log.d(LOG_TAG, "Change rate " + mScale[step]); } mSoundPool.stop(id); + if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); if (DEBUG) Log.d(LOG_TAG, "End rate test"); return true; } @@ -205,34 +206,38 @@ public class SoundPoolTest extends Activity Log.e(LOG_TAG, "Error occurred starting note"); return false; } - sleep(250); + sleep(1000); // play a low priority sound - int id = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME, + int id = mSoundPool.play(mSounds[1], DEFAULT_VOLUME, DEFAULT_VOLUME, LOW_PRIORITY, DEFAULT_LOOP, 1.0f); - if (id > 0) { + if (id != 0) { Log.e(LOG_TAG, "Normal > Low priority test failed"); result = false; mSoundPool.stop(id); } else { - Log.e(LOG_TAG, "Normal > Low priority test passed"); + sleep(1000); + Log.i(LOG_TAG, "Normal > Low priority test passed"); } - sleep(250); // play a high priority sound - id = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME, + id = mSoundPool.play(mSounds[2], DEFAULT_VOLUME, DEFAULT_VOLUME, HIGH_PRIORITY, DEFAULT_LOOP, 1.0f); if (id == 0) { Log.e(LOG_TAG, "High > Normal priority test failed"); result = false; } else { - Log.e(LOG_TAG, "High > Normal priority test passed"); + sleep(1000); + Log.i(LOG_TAG, "Stopping high priority"); + mSoundPool.stop(id); + sleep(1000); + Log.i(LOG_TAG, "High > Normal priority test passed"); } - sleep(250); - mSoundPool.stop(id); // stop normal note + Log.i(LOG_TAG, "Stopping normal priority"); mSoundPool.stop(normalId); + sleep(1000); if (DEBUG) Log.d(LOG_TAG, "End priority test"); return result; @@ -250,17 +255,21 @@ public class SoundPoolTest extends Activity Log.e(LOG_TAG, "Error occurred starting note"); return false; } - sleep(250); + sleep(2500); // pause and resume sound a few times for (int count = 0; count < 5; count++) { + if (DEBUG) Log.d(LOG_TAG, "Pause note " + id); mSoundPool.pause(id); - sleep(250); + sleep(1000); + if (DEBUG) Log.d(LOG_TAG, "Resume note " + id); mSoundPool.resume(id); - sleep(250); + sleep(1000); } + if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); mSoundPool.stop(id); + sleep(1000); // play 5 sounds, forces one to be stolen int ids[] = new int[5]; @@ -272,18 +281,21 @@ public class SoundPoolTest extends Activity Log.e(LOG_TAG, "Error occurred starting note"); return false; } - sleep(250); + sleep(1000); } // pause and resume sound a few times for (int count = 0; count < 5; count++) { + if (DEBUG) Log.d(LOG_TAG, "autoPause"); mSoundPool.autoPause(); - sleep(250); + sleep(1000); + if (DEBUG) Log.d(LOG_TAG, "autoResume"); mSoundPool.autoResume(); - sleep(250); + sleep(1000); } for (int i = 0; i < 5; i++) { + if (DEBUG) Log.d(LOG_TAG, "Stop note " + ids[i]); mSoundPool.stop(ids[i]); } @@ -302,9 +314,9 @@ public class SoundPoolTest extends Activity return false; } - // pan from left to right + // pan from right to left for (int count = 0; count < 101; count++) { - sleep(20); + sleep(50); double radians = PI_OVER_2 * count / 100.0; float leftVolume = (float) Math.sin(radians); float rightVolume = (float) Math.cos(radians); diff --git a/media/tests/audiotests/Android.mk b/media/tests/audiotests/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..69f0bb5d185bee4fd3e2df32526f0dbdc6344812 --- /dev/null +++ b/media/tests/audiotests/Android.mk @@ -0,0 +1,21 @@ +ifeq ($(TARGET_ARCH),arm) + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE:= shared_mem_test +LOCAL_SRC_FILES := \ + shared_mem_test.cpp +LOCAL_SHARED_LIBRARIES := \ + libc \ + libcutils \ + libutils \ + libbinder \ + libhardware_legacy \ + libmedia +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) + +endif diff --git a/media/tests/audiotests/shared_mem_test.cpp b/media/tests/audiotests/shared_mem_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..992c900d31a2325ac3b7c4190b1948e4fa27dd06 --- /dev/null +++ b/media/tests/audiotests/shared_mem_test.cpp @@ -0,0 +1,216 @@ +// Copyright 2008, 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. + +// +#define LOG_NDEBUG 0 +#define LOG_TAG "shared_mem_test" + +#include +#include +#include +#include +#include +#include + +#include "shared_mem_test.h" +#include +#include +#include +#include + + +#include + +#include + +namespace android { + +/************************************************************ +* +* Constructor +* +************************************************************/ +AudioTrackTest::AudioTrackTest(void) { + + InitSine(); // init sine table + +} + + +/************************************************************ +* +* +************************************************************/ +void AudioTrackTest::Execute(void) { + if (Test01() == 0) { + ALOGD("01 passed\n"); + } else { + ALOGD("01 failed\n"); + } +} + +/************************************************************ +* +* Shared memory test +* +************************************************************/ +#define BUF_SZ 44100 + +int AudioTrackTest::Test01() { + + sp heap; + sp iMem; + uint8_t* p; + + short smpBuf[BUF_SZ]; + long rate = 44100; + unsigned long phi; + unsigned long dPhi; + long amplitude; + long freq = 1237; + float f0; + + f0 = pow(2., 32.) * freq / (float)rate; + dPhi = (unsigned long)f0; + amplitude = 1000; + phi = 0; + Generate(smpBuf, BUF_SZ, amplitude, phi, dPhi); // fill buffer + + for (int i = 0; i < 1024; i++) { + heap = new MemoryDealer(1024*1024, "AudioTrack Heap Base"); + + iMem = heap->allocate(BUF_SZ*sizeof(short)); + + p = static_cast(iMem->pointer()); + memcpy(p, smpBuf, BUF_SZ*sizeof(short)); + + sp track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type + rate, + AUDIO_FORMAT_PCM_16_BIT,// word length, PCM + AUDIO_CHANNEL_OUT_MONO, + iMem); + + status_t status = track->initCheck(); + if(status != NO_ERROR) { + track.clear(); + ALOGD("Failed for initCheck()"); + return -1; + } + + // start play + ALOGD("start"); + track->start(); + + usleep(20000); + + ALOGD("stop"); + track->stop(); + iMem.clear(); + heap.clear(); + usleep(20000); + } + + return 0; + +} + +/************************************************************ +* +* Generate a mono buffer +* Error is less than 3lsb +* +************************************************************/ +void AudioTrackTest::Generate(short *buffer, long bufferSz, long amplitude, unsigned long &phi, long dPhi) +{ + long pi13 = 25736; // 2^13*pi + // fill buffer + for(int i0=0; i0>22) & 0x3ff]) >> 15; + // correct with interpolation + l0 = (phi>>12) & 0x3ff; // 2^20 * x / (2*pi) + l1 = (amplitude*sin1024[((phi>>22) + 256) & 0x3ff]) >> 15; // 2^15*cosine + l0 = (l0 * l1) >> 10; + l0 = (l0 * pi13) >> 22; + sample = sample + l0; + + return (short)sample; +} + + +/************************************************************ +* +* init sine table +* +************************************************************/ +void AudioTrackTest::InitSine(void) { + double phi = 0; + double dPhi = 2 * M_PI / SIN_SZ; + for(int i0 = 0; i0= 32767) d0 = 32767; + if(d0 <= -32768) d0 = -32768; + sin1024[i0] = (short)d0; + } +} + +/************************************************************ +* +* main in name space +* +************************************************************/ +int main() { + ProcessState::self()->startThreadPool(); + AudioTrackTest *test; + + test = new AudioTrackTest(); + test->Execute(); + delete test; + + return 0; +} + +} + +/************************************************************ +* +* global main +* +************************************************************/ +int main(int argc, char *argv[]) { + + return android::main(); +} diff --git a/media/tests/audiotests/shared_mem_test.h b/media/tests/audiotests/shared_mem_test.h new file mode 100644 index 0000000000000000000000000000000000000000..f4959557b0b57d40ed403696d56fcf8bb31bc40b --- /dev/null +++ b/media/tests/audiotests/shared_mem_test.h @@ -0,0 +1,27 @@ +// Copyright 2008 The Android Open Source Project + +#ifndef AUDIOTRACKTEST_H_ +#define AUDIOTRACKTEST_H_ + +namespace android { + +class AudioTrackTest{ + public: + AudioTrackTest(void); + ~AudioTrackTest() {}; + + void Execute(void); + int Test01(); + + void Generate(short *buffer, long bufferSz, long amplitude, unsigned long &phi, long dPhi); + void InitSine(); + short ComputeSine(long amplitude, long phi); + + #define SIN_SZ 1024 + short sin1024[SIN_SZ]; // sine table 2*pi = 1024 +}; + +}; + + +#endif /*AUDIOTRACKTEST_H_*/ diff --git a/native/android/Android.mk b/native/android/Android.mk index 207cc4ba140edb929512db45f6e025edf7867a4c..cda38e06270ba60eb1a5b43060213ac7fae24049 100644 --- a/native/android/Android.mk +++ b/native/android/Android.mk @@ -20,6 +20,7 @@ LOCAL_SHARED_LIBRARIES := \ liblog \ libcutils \ libandroidfw \ + libinput \ libutils \ libbinder \ libui \ diff --git a/native/android/input.cpp b/native/android/input.cpp index f6ea5769869ff5559651300ebf2d858503cdba0f..e9d08b4d839f5089a5d6e74c5be7097abee8ee82 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -18,8 +18,8 @@ #include #include -#include -#include +#include +#include #include #include #include diff --git a/nfc-extras/Android.mk b/nfc-extras/Android.mk index 131d8985f6d5a0e3924f1ba99bcb80c062c36288..330e2d4ec17dcc0a4078d4aec8adaaf8c7e726ae 100644 --- a/nfc-extras/Android.mk +++ b/nfc-extras/Android.mk @@ -9,6 +9,3 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_MODULE:= com.android.nfc_extras include $(BUILD_JAVA_LIBRARY) - -# put the classes.jar, with full class files instead of classes.dex inside, into the dist directory -$(call dist-for-goals, droidcore, $(full_classes_jar):com.android.nfc_extras.jar) diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java index 05b498c1357f29486cc264e592e0dc616d014741..0c65283a5ea513682a9ae8d3265bd137061e896e 100644 --- a/obex/javax/obex/ClientOperation.java +++ b/obex/javax/obex/ClientOperation.java @@ -121,6 +121,13 @@ public final class ClientOperation implements Operation, BaseStream { (header).mAuthResp.length); } + + if ((header).mConnectionID != null) { + mRequestHeader.mConnectionID = new byte[4]; + System.arraycopy((header).mConnectionID, 0, mRequestHeader.mConnectionID, 0, + 4); + + } } /** @@ -420,7 +427,7 @@ public final class ClientOperation implements Operation, BaseStream { //split the headerArray end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize - ObexHelper.BASE_PACKET_LENGTH); - // can not split + // can not split if (end == -1) { mOperationDone = true; abort(); @@ -521,7 +528,7 @@ public final class ClientOperation implements Operation, BaseStream { return false; } - // send all of the output data in 0x48, + // send all of the output data in 0x48, // send 0x49 with empty body if ((mPrivateOutput != null) && (mPrivateOutput.size() > 0)) returnValue = true; @@ -723,4 +730,7 @@ public final class ClientOperation implements Operation, BaseStream { } } } + + public void noBodyHeader(){ + } } diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java index b89b707266fb66e4072c7596c6e2606737344a67..2b3066f0bda5e11e16b2f113d6296ac337c30837 100644 --- a/obex/javax/obex/HeaderSet.java +++ b/obex/javax/obex/HeaderSet.java @@ -464,6 +464,8 @@ public final class HeaderSet { return mHttpHeader; case WHO: return mWho; + case CONNECTION_ID: + return mConnectionID; case OBJECT_CLASS: return mObjectClass; case APPLICATION_PARAMETER: diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java index 25656ed5d6ab790eb85a24f8366aca7d619f5555..5b4d5ace3669af8f235d0c9c9495f5fab46770ac 100644 --- a/obex/javax/obex/Operation.java +++ b/obex/javax/obex/Operation.java @@ -178,4 +178,6 @@ public interface Operation { void close() throws IOException; int getMaxPacketSize(); + + public void noBodyHeader(); } diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java index d1476d271cfccbf6057a4fe4332984cec62c2794..fc441e0e165c75351a6493fe4c1f8a5f10c143ef 100644 --- a/obex/javax/obex/ServerOperation.java +++ b/obex/javax/obex/ServerOperation.java @@ -88,6 +88,8 @@ public final class ServerOperation implements Operation, BaseStream { private boolean mHasBody; + private boolean mSendBodyHeader = true; + /** * Creates new ServerOperation * @param p the parent that created this object @@ -364,24 +366,33 @@ public final class ServerOperation implements Operation, BaseStream { * (End of Body) otherwise, we need to send 0x48 (Body) */ if ((finalBitSet) || (mPrivateOutput.isClosed())) { - out.write(0x49); + if(mSendBodyHeader == true) { + out.write(0x49); + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + out.write(body); + } } else { + if(mSendBodyHeader == true) { out.write(0x48); + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + out.write(body); + } } - bodyLength += 3; - out.write((byte)(bodyLength >> 8)); - out.write((byte)bodyLength); - out.write(body); } } if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { - out.write(0x49); - orginalBodyLength = 3; - out.write((byte)(orginalBodyLength >> 8)); - out.write((byte)orginalBodyLength); - + if(mSendBodyHeader == true) { + out.write(0x49); + orginalBodyLength = 3; + out.write((byte)(orginalBodyLength >> 8)); + out.write((byte)orginalBodyLength); + } } mResponseSize = 3; @@ -711,4 +722,8 @@ public final class ServerOperation implements Operation, BaseStream { public void streamClosed(boolean inStream) throws IOException { } + + public void noBodyHeader(){ + mSendBodyHeader = false; + } } diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java index a4b9759588a4b8e4ec5c14480f493581024ca900..f1b9a0d6c4d6c603679383412bc14abe2ab4ae6e 100644 --- a/obex/javax/obex/ServerSession.java +++ b/obex/javax/obex/ServerSession.java @@ -256,6 +256,10 @@ public final class ServerSession extends ObexSession implements Runnable { public void sendResponse(int code, byte[] header) throws IOException { int totalLength = 3; byte[] data = null; + OutputStream op = mOutput; + if (op == null) { + return; + } if (header != null) { totalLength += header.length; @@ -270,8 +274,8 @@ public final class ServerSession extends ObexSession implements Runnable { data[1] = (byte)0x00; data[2] = (byte)totalLength; } - mOutput.write(data); - mOutput.flush(); + op.write(data); + op.flush(); } /** diff --git a/opengl/java/android/opengl/EGLConfig.java b/opengl/java/android/opengl/EGLConfig.java index d457c9f3d052456a3affa9589c7c8bf550e57081..a7a6bbb8c3d4476ea5b34841b459977e855a2b3f 100644 --- a/opengl/java/android/opengl/EGLConfig.java +++ b/opengl/java/android/opengl/EGLConfig.java @@ -29,7 +29,7 @@ public class EGLConfig extends EGLObjectHandle { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof EGLConfig)) return false; EGLConfig that = (EGLConfig) o; return getHandle() == that.getHandle(); diff --git a/opengl/java/android/opengl/EGLContext.java b/opengl/java/android/opengl/EGLContext.java index 41b8ef161a0ecea1128373538987e90a771b66ad..c93bd6ea7725c2cd1cc754edfcbabc6bff95a5d7 100644 --- a/opengl/java/android/opengl/EGLContext.java +++ b/opengl/java/android/opengl/EGLContext.java @@ -29,7 +29,7 @@ public class EGLContext extends EGLObjectHandle { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof EGLContext)) return false; EGLContext that = (EGLContext) o; return getHandle() == that.getHandle(); diff --git a/opengl/java/android/opengl/EGLDisplay.java b/opengl/java/android/opengl/EGLDisplay.java index 17d1a641e22d4ff1f800e6bb02cca8d9fa16d1ed..5b8043a304c06ecb48f29147035215157eb3e1f5 100644 --- a/opengl/java/android/opengl/EGLDisplay.java +++ b/opengl/java/android/opengl/EGLDisplay.java @@ -29,7 +29,7 @@ public class EGLDisplay extends EGLObjectHandle { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof EGLDisplay)) return false; EGLDisplay that = (EGLDisplay) o; return getHandle() == that.getHandle(); diff --git a/opengl/java/android/opengl/EGLSurface.java b/opengl/java/android/opengl/EGLSurface.java index 65bec4f1e1cbee96f86e810033217113ae4d9575..c379dc9864648201fb9c0d230d828336d0e875f7 100644 --- a/opengl/java/android/opengl/EGLSurface.java +++ b/opengl/java/android/opengl/EGLSurface.java @@ -29,7 +29,7 @@ public class EGLSurface extends EGLObjectHandle { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof EGLSurface)) return false; EGLSurface that = (EGLSurface) o; return getHandle() == that.getHandle(); diff --git a/opengl/java/android/opengl/Matrix.java b/opengl/java/android/opengl/Matrix.java index 72128acbcb610d308d752770d0fc65bf8154bf70..ce3f57ebfea1d313b88fe4a94d73cc67ce8c1a92 100644 --- a/opengl/java/android/opengl/Matrix.java +++ b/opengl/java/android/opengl/Matrix.java @@ -19,24 +19,21 @@ package android.opengl; /** * Matrix math utilities. These methods operate on OpenGL ES format * matrices and vectors stored in float arrays. - * + *

        * Matrices are 4 x 4 column-vector matrices stored in column-major * order: *

          *  m[offset +  0] m[offset +  4] m[offset +  8] m[offset + 12]
          *  m[offset +  1] m[offset +  5] m[offset +  9] m[offset + 13]
          *  m[offset +  2] m[offset +  6] m[offset + 10] m[offset + 14]
        - *  m[offset +  3] m[offset +  7] m[offset + 11] m[offset + 15]
        - * 
        + * m[offset + 3] m[offset + 7] m[offset + 11] m[offset + 15] * - * Vectors are 4 row x 1 column column-vectors stored in order: + * Vectors are 4 x 1 column vectors stored in order: *
          * v[offset + 0]
          * v[offset + 1]
          * v[offset + 2]
        - * v[offset + 3]
        - * 
        - * + * v[offset + 3] */ public class Matrix { @@ -44,12 +41,18 @@ public class Matrix { private final static float[] sTemp = new float[32]; /** - * Multiply two 4x4 matrices together and store the result in a third 4x4 + * @deprecated All methods are static, do not instantiate this class. + */ + @Deprecated + public Matrix() {} + + /** + * Multiplies two 4x4 matrices together and stores the result in a third 4x4 * matrix. In matrix notation: result = lhs x rhs. Due to the way * matrix multiplication works, the result matrix will have the same * effect as first multiplying by the rhs matrix, then multiplying by * the lhs matrix. This is the opposite of what you might expect. - * + *

        * The same float array may be passed for result, lhs, and/or rhs. However, * the result element values are undefined if the result elements overlap * either the lhs or rhs elements. @@ -70,9 +73,9 @@ public class Matrix { float[] lhs, int lhsOffset, float[] rhs, int rhsOffset); /** - * Multiply a 4 element vector by a 4x4 matrix and store the result in a 4 - * element column vector. In matrix notation: result = lhs x rhs - * + * Multiplies a 4 element vector by a 4x4 matrix and stores the result in a + * 4-element column vector. In matrix notation: result = lhs x rhs + *

        * The same float array may be passed for resultVec, lhsMat, and/or rhsVec. * However, the resultVec element values are undefined if the resultVec * elements overlap either the lhsMat or rhsVec elements. @@ -97,12 +100,14 @@ public class Matrix { /** * Transposes a 4 x 4 matrix. + *

        + * mTrans and m must not overlap. * - * @param mTrans the array that holds the output inverted matrix - * @param mTransOffset an offset into mInv where the inverted matrix is + * @param mTrans the array that holds the output transposed matrix + * @param mTransOffset an offset into mTrans where the transposed matrix is * stored. * @param m the input array - * @param mOffset an offset into m where the matrix is stored. + * @param mOffset an offset into m where the input matrix is stored. */ public static void transposeM(float[] mTrans, int mTransOffset, float[] m, int mOffset) { @@ -117,12 +122,14 @@ public class Matrix { /** * Inverts a 4 x 4 matrix. + *

        + * mInv and m must not overlap. * * @param mInv the array that holds the output inverted matrix * @param mInvOffset an offset into mInv where the inverted matrix is * stored. * @param m the input array - * @param mOffset an offset into m where the matrix is stored. + * @param mOffset an offset into m where the input matrix is stored. * @return true if the matrix could be inverted, false if it could not. */ public static boolean invertM(float[] mInv, int mInvOffset, float[] m, @@ -301,10 +308,11 @@ public class Matrix { /** - * Define a projection matrix in terms of six clip planes - * @param m the float array that holds the perspective matrix + * Defines a projection matrix in terms of six clip planes. + * + * @param m the float array that holds the output perspective matrix * @param offset the offset into float array m where the perspective - * matrix data is written + * matrix data is written * @param left * @param right * @param bottom @@ -358,11 +366,12 @@ public class Matrix { } /** - * Define a projection matrix in terms of a field of view angle, an - * aspect ratio, and z clip planes + * Defines a projection matrix in terms of a field of view angle, an + * aspect ratio, and z clip planes. + * * @param m the float array that holds the perspective matrix * @param offset the offset into float array m where the perspective - * matrix data is written + * matrix data is written * @param fovy field of view in y direction, in degrees * @param aspect width to height aspect ratio of the viewport * @param zNear @@ -395,7 +404,7 @@ public class Matrix { } /** - * Computes the length of a vector + * Computes the length of a vector. * * @param x x coordinate of a vector * @param y y coordinate of a vector @@ -408,6 +417,7 @@ public class Matrix { /** * Sets matrix m to the identity matrix. + * * @param sm returns the result * @param smOffset index into sm where the result matrix starts */ @@ -421,7 +431,10 @@ public class Matrix { } /** - * Scales matrix m by x, y, and z, putting the result in sm + * Scales matrix m by x, y, and z, putting the result in sm. + *

        + * m and sm must not overlap. + * * @param sm returns the result * @param smOffset index into sm where the result matrix starts * @param m source matrix @@ -444,7 +457,8 @@ public class Matrix { } /** - * Scales matrix m in place by sx, sy, and sz + * Scales matrix m in place by sx, sy, and sz. + * * @param m matrix to scale * @param mOffset index into m where the matrix starts * @param x scale factor x @@ -462,7 +476,10 @@ public class Matrix { } /** - * Translates matrix m by x, y, and z, putting the result in tm + * Translates matrix m by x, y, and z, putting the result in tm. + *

        + * m and tm must not overlap. + * * @param tm returns the result * @param tmOffset index into sm where the result matrix starts * @param m source matrix @@ -487,6 +504,7 @@ public class Matrix { /** * Translates matrix m by x, y, and z in place. + * * @param m matrix * @param mOffset index into m where the matrix starts * @param x translation factor x @@ -503,15 +521,18 @@ public class Matrix { } /** - * Rotates matrix m by angle a (in degrees) around the axis (x, y, z) + * Rotates matrix m by angle a (in degrees) around the axis (x, y, z). + *

        + * m and rm must not overlap. + * * @param rm returns the result * @param rmOffset index into rm where the result matrix starts * @param m source matrix * @param mOffset index into m where the source matrix starts * @param a angle to rotate in degrees - * @param x scale factor x - * @param y scale factor y - * @param z scale factor z + * @param x X axis component + * @param y Y axis component + * @param z Z axis component */ public static void rotateM(float[] rm, int rmOffset, float[] m, int mOffset, @@ -524,13 +545,14 @@ public class Matrix { /** * Rotates matrix m in place by angle a (in degrees) - * around the axis (x, y, z) + * around the axis (x, y, z). + * * @param m source matrix * @param mOffset index into m where the matrix starts * @param a angle to rotate in degrees - * @param x scale factor x - * @param y scale factor y - * @param z scale factor z + * @param x X axis component + * @param y Y axis component + * @param z Z axis component */ public static void rotateM(float[] m, int mOffset, float a, float x, float y, float z) { @@ -542,13 +564,18 @@ public class Matrix { } /** - * Rotates matrix m by angle a (in degrees) around the axis (x, y, z) + * Creates a matrix for rotation by angle a (in degrees) + * around the axis (x, y, z). + *

        + * An optimized path will be used for rotation about a major axis + * (e.g. x=1.0f y=0.0f z=0.0f). + * * @param rm returns the result * @param rmOffset index into rm where the result matrix starts * @param a angle to rotate in degrees - * @param x scale factor x - * @param y scale factor y - * @param z scale factor z + * @param x X axis component + * @param y Y axis component + * @param z Z axis component */ public static void setRotateM(float[] rm, int rmOffset, float a, float x, float y, float z) { @@ -608,7 +635,8 @@ public class Matrix { } /** - * Converts Euler angles to a rotation matrix + * Converts Euler angles to a rotation matrix. + * * @param rm returns the result * @param rmOffset index into rm where the result matrix starts * @param x angle of rotation, in degrees @@ -651,7 +679,7 @@ public class Matrix { } /** - * Define a viewing transformation in terms of an eye point, a center of + * Defines a viewing transformation in terms of an eye point, a center of * view, and an up vector. * * @param rm returns the result diff --git a/packages/BackupRestoreConfirmation/Android.mk b/packages/BackupRestoreConfirmation/Android.mk index e775b4457831500a85a180a28e047bcb6caed0f5..b84c07f359f46988baca8a7ec972080a993a990a 100644 --- a/packages/BackupRestoreConfirmation/Android.mk +++ b/packages/BackupRestoreConfirmation/Android.mk @@ -23,6 +23,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := BackupRestoreConfirmation LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true include $(BUILD_PACKAGE) diff --git a/packages/BackupRestoreConfirmation/res/values-af/strings.xml b/packages/BackupRestoreConfirmation/res/values-af/strings.xml index fadd12507d71ff74be3ac4444e37ea370fe222bf..0bf54cf994185a0d571267abe183c129497bbf90 100644 --- a/packages/BackupRestoreConfirmation/res/values-af/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-af/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Volledige rugsteun" "Volledige herstel" - "\'n Volledige rugsteun van al die data na \'n rekenaar is aangevra. Wil jy dit toelaat? "\n\n"As jy nie self die rugsteun versoek het nie, moenie toelaat dat die aksie voortgaan nie." + "\'n Volledige rugsteun van al die data na \'n rekenaar is aangevra. Wil jy dit toelaat? \n\nAs jy nie self die rugsteun versoek het nie, moenie toelaat dat die aksie voortgaan nie." "Rugsteun my data" "Moenie rugsteun nie" - "\'n Volle teruglaai van alle data van \'n gekoppelde rekenaar is versoek. Wil jy dit toelaat? "\n\n" As jy nie die teruglaai self versoek het nie, moenie die aksie toelaat nie. Dit sal enige data tans op die toestel vervang!" + "\'n Volle teruglaai van alle data van \'n gekoppelde rekenaar is versoek. Wil jy dit toelaat? \n\n As jy nie die teruglaai self versoek het nie, moenie die aksie toelaat nie. Dit sal enige data tans op die toestel vervang!" "Laai my data terug" "Moenie herstel nie" "Voer asseblief jou huidige rugsteunwagwoord hieronder in:" diff --git a/packages/BackupRestoreConfirmation/res/values-am/strings.xml b/packages/BackupRestoreConfirmation/res/values-am/strings.xml index 512d917bcd1523dad1c2bf9a92022f407d3e6992..ec5b9631229c0f335a983231b2c5409da96dd182 100644 --- a/packages/BackupRestoreConfirmation/res/values-am/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-am/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "ሙሉ ለሙሉ መጠባበቂያ" "ሙሉ ለሙሉ እáŠá‰ áˆ¨á‰ á‰µ መáˆáˆµ" - "áˆáˆ‰áŠ•áˆ á‹áˆ‚ብ በሙሉ መጠበቂያ ከተያያዘ የዴስክቶᕠኮáˆá’ዩተር ተጠይቋáˆá¢ ይህ እንዲከሰት ለመáቀድ á‹­áˆáˆáŒ‹áˆ‰? "\n\n"እርስዎ ራስዎ የመጠባበቂያá‹áŠ• ጥየቃ ካáˆáŒ á‹¨á‰ ክወናዠእንዲካሄድ አይáቀዱá¢" + "áˆáˆ‰áŠ•áˆ á‹áˆ‚ብ በሙሉ መጠበቂያ ከተያያዘ የዴስክቶᕠኮáˆá’ዩተር ተጠይቋáˆá¢ ይህ እንዲከሰት ለመáቀድ á‹­áˆáˆáŒ‹áˆ‰? \n\nእርስዎ ራስዎ የመጠባበቂያá‹áŠ• ጥየቃ ካáˆáŒ á‹¨á‰ ክወናዠእንዲካሄድ አይáቀዱá¢" "á‹áˆ‚ቤን መጠባበቂያ" "መጠባበቂያ አታድርáŒ" - "áˆáˆ‰áŠ•áˆ á‹áˆ‚ብ በሙሉ መጠበቂያ ከተያያዘ የዴስክቶᕠኮáˆá’ዩተር ተጠይቋáˆá¢ ይህ እንዲከሰት ለመáቀድ á‹­áˆáˆáŒ‹áˆ‰? "\n\n"እርስዎ ራስዎ የመጠባበቂያá‹áŠ• ጥየቃ ካáˆáŒ á‹¨á‰ ክወናዠእንዲካሄድ አይáቀዱᢠይህሠአáˆáŠ• በመሣሪያዎ ላይ ያለá‹áŠ• ማንኛá‹áˆ á‹áˆ‚ብ ይተካáˆ!" + "áˆáˆ‰áŠ•áˆ á‹áˆ‚ብ በሙሉ መጠበቂያ ከተያያዘ የዴስክቶᕠኮáˆá’ዩተር ተጠይቋáˆá¢ ይህ እንዲከሰት ለመáቀድ á‹­áˆáˆáŒ‹áˆ‰? \n\nእርስዎ ራስዎ የመጠባበቂያá‹áŠ• ጥየቃ ካáˆáŒ á‹¨á‰ ክወናዠእንዲካሄድ አይáቀዱᢠይህሠአáˆáŠ• በመሣሪያዎ ላይ ያለá‹áŠ• ማንኛá‹áˆ á‹áˆ‚ብ ይተካáˆ!" "á‹áˆ‚ቤን እáŠá‰ áˆ¨á‰ á‰µ መáˆáˆµ" "እáŠá‰ áˆ¨á‰ á‰µ አትመáˆáˆµ" "እባክዎ የአáˆáŠ‘ን የመጠባበቂያ ይለáቃሠከታች ያስገቡá¡" diff --git a/packages/BackupRestoreConfirmation/res/values-ar/strings.xml b/packages/BackupRestoreConfirmation/res/values-ar/strings.xml index 2e9ec408f5d6090730bedccb22beee4cfb42d6a6..6a6edd975817c718220c3eae007f451c4ac1bb48 100644 --- a/packages/BackupRestoreConfirmation/res/values-ar/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-ar/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "نسخ احتياطي بالكامل" "استعادة كاملة" - "تم طلب الاحتÙاظ بنسخة احتياطية كاملة من البيانات على كمبيوتر سطح مكتب متصل. هل تريد السماح بإجراء ذلك؟"\n\n"إذا لم تطلب الاحتÙاظ بنسخة احتياطية بنÙسك، Ùلا تسمح بمتابعة العملية." + "تم طلب الاحتÙاظ بنسخة احتياطية كاملة من البيانات على كمبيوتر سطح مكتب متصل. هل تريد السماح بإجراء ذلك؟\n\nإذا لم تطلب الاحتÙاظ بنسخة احتياطية بنÙسك، Ùلا تسمح بمتابعة العملية." "الاحتÙاظ بنسخة احتياطية من بياناتي" "عدم النسخ الاحتياطي" - "تم طلب استرداد جميع البيانات بالكامل من كمبيوتر سطح مكتب متصل. هل تريد السماح بإجراء ذلك؟"\n\n"إذا لم تطلب الاسترداد بنÙسك، Ùلا تسمح بمتابعة العملية. يؤدي لك إلى استبدال أية بيانات حاليًا على الجهاز." + "تم طلب استرداد جميع البيانات بالكامل من كمبيوتر سطح مكتب متصل. هل تريد السماح بإجراء ذلك؟\n\nإذا لم تطلب الاسترداد بنÙسك، Ùلا تسمح بمتابعة العملية. يؤدي لك إلى استبدال أية بيانات حاليًا على الجهاز." "استرداد بياناتي" "عدم الاسترداد" "الرجاء إدخال كلمة مرور النسخ الاحتياطي أدناه:" diff --git a/packages/BackupRestoreConfirmation/res/values-be/strings.xml b/packages/BackupRestoreConfirmation/res/values-be/strings.xml index 48361257576133d370cb089395b6799eb2148424..188041a08aa53cea0fbc599e35da1a1bb77bbb1d 100644 --- a/packages/BackupRestoreConfirmation/res/values-be/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-be/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Поўнае Ñ€Ñзервовае капіÑванне" "Поўнае аднаўленне" - "Было прапанавана поўнае Ñ€Ñзервовае капіÑванне ÑžÑÑ–Ñ… дадзеных на падлучаным наÑтольным кампутары. Дазволіць гÑта?"\n\n"Калі вы Ñамі не запытвалі Ñ€Ñзервовае капiÑванне, Ñпынiце аперацыю." + "Было прапанавана поўнае Ñ€Ñзервовае капіÑванне ÑžÑÑ–Ñ… дадзеных на падлучаным наÑтольным кампутары. Дазволіць гÑта?\n\nКалі вы Ñамі не запытвалі Ñ€Ñзервовае капiÑванне, Ñпынiце аперацыю." "РÑзервовае капіÑванне дадзеных" "Ðе Ñтвараць Ñ€ÑÐ·ÐµÑ€Ð²Ð¾Ð²Ñ‹Ñ ÐºÐ¾Ð¿Ñ–Ñ–" - "Запытана поўнае аднаўленне ÑžÑÑ–Ñ… дадзеных з падлучанага наÑтольнага кампутара. Дазволіць гÑта?"\n\n"Калі вы Ñамі не запытвалі аднаўленне, не дазвалÑйце працÑгваць аперацыю. ГÑта прывÑдзе да замены Ñкіх-небудзь дадзеных, ÑÐºÑ–Ñ Ð·Ð°Ñ€Ð°Ð· знаходзÑцца на прыладзе." + "Запытана поўнае аднаўленне ÑžÑÑ–Ñ… дадзеных з падлучанага наÑтольнага кампутара. Дазволіць гÑта?\n\nКалі вы Ñамі не запытвалі аднаўленне, не дазвалÑйце працÑгваць аперацыю. ГÑта прывÑдзе да замены Ñкіх-небудзь дадзеных, ÑÐºÑ–Ñ Ð·Ð°Ñ€Ð°Ð· знаходзÑцца на прыладзе." "Ðднавіць мае дадзеныÑ" "Ðе аднаўлÑць" "УвÑдзіце ваш бÑгучы пароль Ñ€Ñзервовага капіÑÐ²Ð°Ð½Ð½Ñ Ð½Ñ–Ð¶Ñй:" diff --git a/packages/BackupRestoreConfirmation/res/values-bg/strings.xml b/packages/BackupRestoreConfirmation/res/values-bg/strings.xml index a431bf70c6e3c8e0cd416850661d2a8669355ac1..c332774520bb9bd4f063f49e77aed4742dae7132 100644 --- a/packages/BackupRestoreConfirmation/res/values-bg/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-bg/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Пълно резервно копие" "Пълно възÑтановÑване" - "Бе поиÑкано пълно резервно копие на вÑичките данни до Ñвързан наÑтолен компютър. ИÑкате ли да разрешите това?"\n\n"Ðко не Ñте заÑвили Ñъздаването на копие, не позволÑвайте операциÑта да продължи." + "Бе поиÑкано пълно резервно копие на вÑичките данни до Ñвързан наÑтолен компютър. ИÑкате ли да разрешите това?\n\nÐко не Ñте заÑвили Ñъздаването на копие, не позволÑвайте операциÑта да продължи." "Резервно копие на данните ми" "Без резервно копие" - "Бе поиÑкано пълно възÑтановÑване на вÑичките данни от Ñвързан наÑтолен компютър. ИÑкате ли да разрешите това?"\n\n"Ðко не Ñте заÑвили възÑтановÑването, не позволÑвайте операциÑта да продължи. Това ще замеÑти данните, които понаÑтоÑщем Ñа в уÑтройÑтвото!" + "Бе поиÑкано пълно възÑтановÑване на вÑичките данни от Ñвързан наÑтолен компютър. ИÑкате ли да разрешите това?\n\nÐко не Ñте заÑвили възÑтановÑването, не позволÑвайте операциÑта да продължи. Това ще замеÑти данните, които понаÑтоÑщем Ñа в уÑтройÑтвото!" "ВъзÑтановÑване на данните ми" "Без възÑтановÑване" "МолÑ, въведете текущата Ñи парола за резервно копие по-долу:" diff --git a/packages/BackupRestoreConfirmation/res/values-ca/strings.xml b/packages/BackupRestoreConfirmation/res/values-ca/strings.xml index 32cfefca4f3dd472fa423563c4181f963cbd667f..359df47acef9190c234470905ef208225c65be4f 100644 --- a/packages/BackupRestoreConfirmation/res/values-ca/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-ca/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Còpia de seguretat completa" "Restaura completament" - "S\'ha sol·licitat una còpia de seguretat completa de totes les dades a un equip de sobretaula connectat. Vols permetre que això passi?"\n" "\n"Si no has sol·licitat la còpia de seguretat tu mateix, no permetis que continuï l\'operació." + "S\'ha sol·licitat una còpia de seguretat completa de totes les dades a un equip de sobretaula connectat. Vols permetre que això passi?\n \nSi no has sol·licitat la còpia de seguretat tu mateix, no permetis que continuï l\'operació." "Còpia de seguretat de dades" "No en facis una còpia de seguretat" - "S\'ha sol·licitat una restauració completa de totes les dades d\'un equip d\'escriptori connectat. Vols permetre que això passi?"\n" "\n"Si no has sol·licitat la restauració tu mateix, no permetis que continuï l\'operació. Això reemplaçarà les dades que hi hagi actualment a l\'equip." + "S\'ha sol·licitat una restauració completa de totes les dades d\'un equip d\'escriptori connectat. Vols permetre que això passi?\n \nSi no has sol·licitat la restauració tu mateix, no permetis que continuï l\'operació. Això reemplaçarà les dades que hi hagi actualment a l\'equip." "Restaura les meves dades" "No ho restauris" "Introdueix la teva contrasenya actual de còpia de seguretat a continuació:" diff --git a/packages/BackupRestoreConfirmation/res/values-cs/strings.xml b/packages/BackupRestoreConfirmation/res/values-cs/strings.xml index 0732cd8d131600db193ea025d7c2c258c69f05e6..c46916b42e1f1d04a6f61c415f033e44a8ddc4a4 100644 --- a/packages/BackupRestoreConfirmation/res/values-cs/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-cs/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Úplná záloha" "Úplné obnovení" - "Obdrželi jsme požadavek na úplnou zálohu vÅ¡ech dat do pÅ™ipojeného poÄítaÄe. Chcete tuto akci povolit? "\n\n"Pokud jste o zálohu nežádali, operaci nepovolujte." + "Obdrželi jsme požadavek na úplnou zálohu vÅ¡ech dat do pÅ™ipojeného poÄítaÄe. Chcete tuto akci povolit? \n\nPokud jste o zálohu nežádali, operaci nepovolujte." "Zálohovat data" "Nezálohovat" - "Obdrželi jsme žádost o úplné obnovení vÅ¡ech dat z pÅ™ipojeného poÄítaÄe. Chcete tuto akci povolit?"\n\n"Pokud jste o obnovení nepožádali, operaci nepovolujte. Tato akce nahradí veÅ¡kerá data v zařízení." + "Obdrželi jsme žádost o úplné obnovení vÅ¡ech dat z pÅ™ipojeného poÄítaÄe. Chcete tuto akci povolit?\n\nPokud jste o obnovení nepožádali, operaci nepovolujte. Tato akce nahradí veÅ¡kerá data v zařízení." "Obnovit moje data" "Neobnovovat" "Zadejte prosím aktuální heslo pro zálohy níže:" diff --git a/packages/BackupRestoreConfirmation/res/values-da/strings.xml b/packages/BackupRestoreConfirmation/res/values-da/strings.xml index b3756d952963d53addd47e0b82ee37777cca79a8..0a84c306b26a97955d1fa6b49fbe259b1b8f19b7 100644 --- a/packages/BackupRestoreConfirmation/res/values-da/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-da/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fuld sikkerhedskopiering" "Fuld genoprettelse" - "Der er anmodet om en fuld sikkerhedskopiering af alle data til en tilsluttet stationær computer. Vil du tillade dette?"\n\n"Hvis du ikke har anmodet om sikkerhedskopiering, skal du ikke tillade denne handling." + "Der er anmodet om en fuld sikkerhedskopiering af alle data til en tilsluttet stationær computer. Vil du tillade dette?\n\nHvis du ikke har anmodet om sikkerhedskopiering, skal du ikke tillade denne handling." "Sikkerhedskopier mine data" "Undlad at sikkerhedskopiere" - "Der er anmodet om en fuld sikkerhedskopiering af alle data til en tilsluttet stationær computer. Vil du tillade dette?"\n\n"Hvis du ikke har anmodet om sikkerhedskopiering, skal du ikke tillade denne handling." + "Der er anmodet om en fuld sikkerhedskopiering af alle data til en tilsluttet stationær computer. Vil du tillade dette?\n\nHvis du ikke har anmodet om sikkerhedskopiering, skal du ikke tillade denne handling." "Gendan mine data" "Gendan ikke" "Indtast din aktuelle adgangskode til sikkerhedskopiering nedenfor:" diff --git a/packages/BackupRestoreConfirmation/res/values-de/strings.xml b/packages/BackupRestoreConfirmation/res/values-de/strings.xml index a8f2d132288dda9f1ba5f5c9ba9300f885950ea5..91a1dffe9e17e99f8568db1d45fa1679ff9be2e1 100644 --- a/packages/BackupRestoreConfirmation/res/values-de/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-de/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Vollständige Sicherung" "Vollständige Wiederherstellung" - "Es wurde eine vollständige Sicherung sämtlicher Daten auf einen verbundenen Desktop-Computer angefordert. Möchten Sie dies zulassen?"\n\n"Wenn Sie die Sicherung nicht selbst angefordert haben, sollten Sie dem Vorgang nicht zustimmen." + "Es wurde eine vollständige Sicherung sämtlicher Daten auf einen verbundenen Desktop-Computer angefordert. Möchten Sie dies zulassen?\n\nWenn Sie die Sicherung nicht selbst angefordert haben, sollten Sie dem Vorgang nicht zustimmen." "Meine Daten sichern" "Nicht sichern" - "Es wurde eine vollständige Wiederherstellung aller Daten von einem verbundenen Desktop-Computer angefordert. Möchten Sie dies zulassen?"\n\n"Wenn Sie die Wiederherstellung nicht selbst angefordert haben, sollten Sie dem Vorgang nicht zustimmen. Dadurch werden alle zurzeit auf dem Gerät befindlichen Daten ersetzt!" + "Es wurde eine vollständige Wiederherstellung aller Daten von einem verbundenen Desktop-Computer angefordert. Möchten Sie dies zulassen?\n\nWenn Sie die Wiederherstellung nicht selbst angefordert haben, sollten Sie dem Vorgang nicht zustimmen. Dadurch werden alle zurzeit auf dem Gerät befindlichen Daten ersetzt!" "Meine Daten wiederherstellen" "Nicht wiederherstellen" "Geben Sie Ihr aktuelles Sicherungspasswort unten ein:" diff --git a/packages/BackupRestoreConfirmation/res/values-el/strings.xml b/packages/BackupRestoreConfirmation/res/values-el/strings.xml index 13a78cbf7f99b2a99556cc19616d8b7945cad539..cd325ef88aee35e844a6a6aed333b8c8045df1ad 100644 --- a/packages/BackupRestoreConfirmation/res/values-el/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-el/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "ΠλήÏης δημιουÏγία αντιγÏάφων ασφαλείας" "ΠλήÏης επαναφοÏά" - "Έχει ζητηθεί ένα πλήÏες αντίγÏαφο ασφαλείας όλων των δεδομένων σε έναν συνδεδεμένο επιτÏαπέζιο υπολογιστή. Θέλετε να επιτÏαπεί αυτή η ενέÏγεια;"\n\n"Αν δεν έχετε ζητήσει οι ίδιοι αυτό το αντίγÏαφο ασφαλείας, μην επιτÏέψετε την ενέÏγεια." + "Έχει ζητηθεί ένα πλήÏες αντίγÏαφο ασφαλείας όλων των δεδομένων σε έναν συνδεδεμένο επιτÏαπέζιο υπολογιστή. Θέλετε να επιτÏαπεί αυτή η ενέÏγεια;\n\nΑν δεν έχετε ζητήσει οι ίδιοι αυτό το αντίγÏαφο ασφαλείας, μην επιτÏέψετε την ενέÏγεια." "ΔημιουÏγία αντιγÏάφων ασφαλείας για τα δεδομένα μου" "Îα μην δημιουÏγείται αντίγÏαφο ασφαλείας" - "Έχει ζητηθεί πλήÏης επαναφοÏά όλων των δεδομένων από έναν συνδεδεμένο επιτÏαπέζιο υπολογιστή. Θέλετε να επιτÏέψετε αυτήν την ενέÏγεια;"\n\n"Αν δεν έχετε ζητήσει οι ίδιοι αυτήν την επαναφοÏά, μην επιτÏέψετε την ενέÏγεια. Θα γίνει αντικατάσταση τυχόν δεδομένων τα οποία υπάÏχουν αυτήν τη στιγμή στη συσκευή σας!" + "Έχει ζητηθεί πλήÏης επαναφοÏά όλων των δεδομένων από έναν συνδεδεμένο επιτÏαπέζιο υπολογιστή. Θέλετε να επιτÏέψετε αυτήν την ενέÏγεια;\n\nΑν δεν έχετε ζητήσει οι ίδιοι αυτήν την επαναφοÏά, μην επιτÏέψετε την ενέÏγεια. Θα γίνει αντικατάσταση τυχόν δεδομένων τα οποία υπάÏχουν αυτήν τη στιγμή στη συσκευή σας!" "Αποκατάσταση των δεδομένων μου" "Îα μην γίνει επαναφοÏά" "Εισαγάγετε τον Ï„Ïέχοντα κωδικό Ï€Ïόσβασής αντιγÏάφων ασφαλείας παÏακάτω:" diff --git a/packages/BackupRestoreConfirmation/res/values-en-rGB/strings.xml b/packages/BackupRestoreConfirmation/res/values-en-rGB/strings.xml index 7b94e2ee27dcea0e280272ecefbf4dda32d3c364..d096d98a1a515f805c5be08c85cd29bdc54d2cd4 100644 --- a/packages/BackupRestoreConfirmation/res/values-en-rGB/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-en-rGB/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Full backup" "Full restoration" - "A full backup of all data to a connected desktop computer has been requested. Do you want to allow this to happen?"\n\n"If you did not request the backup yourself, do not allow the operation to proceed." + "A full backup of all data to a connected desktop computer has been requested. Do you want to allow this to happen?\n\nIf you did not request the backup yourself, do not allow the operation to proceed." "Back up my data" "Do not back up" - "A full restore of all data from a connected desktop computer has been requested. Do you want to allow this to happen?"\n\n"If you did not request the restore yourself, do not allow the operation to proceed. This will replace any data currently on the device!" + "A full restore of all data from a connected desktop computer has been requested. Do you want to allow this to happen?\n\nIf you did not request the restore yourself, do not allow the operation to proceed. This will replace any data currently on the device!" "Restore my data" "Do not restore" "Please enter your current backup password below:" diff --git a/packages/BackupRestoreConfirmation/res/values-es-rUS/strings.xml b/packages/BackupRestoreConfirmation/res/values-es-rUS/strings.xml index 21afa3115db9dc15748f206b042ffda47d4b9c4c..13ce1da57050e4dc368963729653779eb1e00d18 100644 --- a/packages/BackupRestoreConfirmation/res/values-es-rUS/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-es-rUS/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Realización de la copia de seguridad completa" "Restauración completa" - "Se ha solicitado una copia de seguridad completa de todos los datos en una computadora de escritorio conectada. ¿Deseas permitirla?"\n\n"Si tú no has solicitado la copia de seguridad, no permitas que se realice la operación." + "Se ha solicitado una copia de seguridad completa de todos los datos en una computadora de escritorio conectada. ¿Deseas permitirla?\n\nSi tú no has solicitado la copia de seguridad, no permitas que se realice la operación." "Copia de seguridad de mis datos" "No realizar una copia de seguridad" - "Se ha solicitado una restauración completa de todos los datos de una computadora de escritorio conectada. ¿Deseas permitirla?"\n\n"Si tú no has solicitado la restauración, no permitas que se realice la operación. Esto reemplazaría los datos actuales del dispositivo." + "Se ha solicitado una restauración completa de todos los datos de una computadora de escritorio conectada. ¿Deseas permitirla?\n\nSi tú no has solicitado la restauración, no permitas que se realice la operación. Esto reemplazaría los datos actuales del dispositivo." "Restaurar mis datos" "No restaurar" "Introduce tu contraseña actual de copia de seguridad a continuación:" diff --git a/packages/BackupRestoreConfirmation/res/values-es/strings.xml b/packages/BackupRestoreConfirmation/res/values-es/strings.xml index ef97e3c964eeafcb43f40dc32a73d7f973be77e3..7ce3990e65a7694d81c608e38f54134f22666b6a 100644 --- a/packages/BackupRestoreConfirmation/res/values-es/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-es/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Copia de seguridad completa" "Restauración completa" - "Se ha solicitado una copia de seguridad completa de todos los datos en un ordenador conectado. ¿Quieres permitir la copia de seguridad?"\n\n"No debes permitir la copia de seguridad si no has realizado tú la solicitud." + "Se ha solicitado una copia de seguridad completa de todos los datos en un ordenador conectado. ¿Quieres permitir la copia de seguridad?\n\nNo debes permitir la copia de seguridad si no has realizado tú la solicitud." "Copia de seguridad de datos" "No hacer copia de seguridad" - "Se ha solicitado una restauración completa de todos los datos desde un ordenador conectado. ¿Quieres permitir la restauración?"\n\n"No debes permitir la restauración si no has realizado tú la solicitud. Se sustituirán los datos actuales del dispositivo." + "Se ha solicitado una restauración completa de todos los datos desde un ordenador conectado. ¿Quieres permitir la restauración?\n\nNo debes permitir la restauración si no has realizado tú la solicitud. Se sustituirán los datos actuales del dispositivo." "Restaurar mis datos" "No restaurar" "Introduce a continuación la contraseña actual de copia de seguridad:" diff --git a/packages/BackupRestoreConfirmation/res/values-et/strings.xml b/packages/BackupRestoreConfirmation/res/values-et/strings.xml index 5a5c454f1c589722cbb1257d3a6beeda04cd64d6..0f5fde2c9a2e2ee33e576eeb4981709a44228b23 100644 --- a/packages/BackupRestoreConfirmation/res/values-et/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-et/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Täielik varundus" "Täielik taastamine" - "Taotleti kõikide andmete varundamist ühendatud lauaarvutist. Kas soovite seda lubada?"\n\n"Kui te ei taotlenud varundust, siis ärge lubage toimingut jätkata." + "Taotleti kõikide andmete varundamist ühendatud lauaarvutist. Kas soovite seda lubada?\n\nKui te ei taotlenud varundust, siis ärge lubage toimingut jätkata." "Varunda mu andmed" "Ära varunda" - "Taotletud on kõikide andmete taastamist ühendatud lauaarvutist. Kas soovite seda lubada?"\n\n"Kui te ei taotlenud taastamist, siis ärge lubage toimingut jätkata. See asendab kõik praegu seadmes olevad andmed." + "Taotletud on kõikide andmete taastamist ühendatud lauaarvutist. Kas soovite seda lubada?\n\nKui te ei taotlenud taastamist, siis ärge lubage toimingut jätkata. See asendab kõik praegu seadmes olevad andmed." "Taasta mu andmed" "Ära taasta" "Sisestage allpool praegune varunduse parool:" diff --git a/packages/BackupRestoreConfirmation/res/values-fa/strings.xml b/packages/BackupRestoreConfirmation/res/values-fa/strings.xml index 43494443b01a4a4a118b998dbad5b07c90bb3ab1..8b20954f6dd2bb678701043a21c44815e52e3541 100644 --- a/packages/BackupRestoreConfirmation/res/values-fa/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-fa/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "پشتیبان‌گیری کامل" "بازیابی کامل" - "درخواست پشتیبان گیری کامل از تمام داده‌ها به یک رایانه دسک‌تاپ متصل داده شده است. آیا می‌خواهید این عمل انجام شود؟"\n\n"اگر شما درخواست تهیهٔ نسخهٔ پشتیبان را نداده‌اید، اجازه‌ ادامه عملیات را ندهید." + "درخواست پشتیبان گیری کامل از تمام داده‌ها به یک رایانه دسک‌تاپ متصل داده شده است. آیا می‌خواهید این عمل انجام شود؟\n\nاگر شما درخواست تهیهٔ نسخهٔ پشتیبان را نداده‌اید، اجازه‌ ادامه عملیات را ندهید." "از داده‌های من نسخهٔ پشتیبان تهیه شود" "نسخهٔ پشتیبان تهیه نشود" - "بازیابی کامل تمام داده‌ها از یک رایانه دسک تاپ متصل درخواست شده است. آیا می‌خواهید این اجازه را بدهید؟"\n\n"اگر خود شما درخواست بازیابی نداده‌اید، اجازه ادامه این عملیات را ندهید. با این کار همه داده‌هایی Ú©Ù‡ اکنون روی دستگاه است جایگزین می‌شود!" + "بازیابی کامل تمام داده‌ها از یک رایانه دسک تاپ متصل درخواست شده است. آیا می‌خواهید این اجازه را بدهید؟\n\nاگر خود شما درخواست بازیابی نداده‌اید، اجازه ادامه این عملیات را ندهید. با این کار همه داده‌هایی Ú©Ù‡ اکنون روی دستگاه است جایگزین می‌شود!" "بازیابی داده‌های من" "بازیابی نشود" "لطÙاً گذرواژه نسخهٔ پشتیبان Ùعلی خود را در زیر وارد کنید:" diff --git a/packages/BackupRestoreConfirmation/res/values-fi/strings.xml b/packages/BackupRestoreConfirmation/res/values-fi/strings.xml index 80da29b4b7073473b8932a92e0e1df1b5412b33a..88aaaa56524624babb847624fd4f0dce3c9725b5 100644 --- a/packages/BackupRestoreConfirmation/res/values-fi/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-fi/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Täysi varmuuskopiointi" "Täysi palautus" - "Kytketyn tietokoneen kaikista tiedoista on pyydetty täydellistä varmuuskopiota. Haluatko sallia tämän?"\n\n"Jos et ole itse pyytänyt varmuuskopiota, älä salli toimintoa." + "Kytketyn tietokoneen kaikista tiedoista on pyydetty täydellistä varmuuskopiota. Haluatko sallia tämän?\n\nJos et ole itse pyytänyt varmuuskopiota, älä salli toimintoa." "Varmuuskopioi omat tiedot" "Älä varmuuskopioi" - "Täydellinen varmuuskopio kytketyn tietokoneen kaikista tiedoista on pyydetty. Haluatko sallia tämän?"\n\n"Jos et ole itse pyytänyt varmuuskopiota, älä salli toimintoa. Tämä korvaa laitteesi kaikki nykyiset tiedot!" + "Täydellinen varmuuskopio kytketyn tietokoneen kaikista tiedoista on pyydetty. Haluatko sallia tämän?\n\nJos et ole itse pyytänyt varmuuskopiota, älä salli toimintoa. Tämä korvaa laitteesi kaikki nykyiset tiedot!" "Palauta omat tiedot" "Älä palauta" "Kirjoita nykyinen varmuuskopioinnin salasana alla:" diff --git a/packages/BackupRestoreConfirmation/res/values-fr/strings.xml b/packages/BackupRestoreConfirmation/res/values-fr/strings.xml index 9af37b0dc407c5819c9aa8ec995b1d9a0c468347..897d8d1d63eade64f26e18eba08eaeb5b22cea77 100644 --- a/packages/BackupRestoreConfirmation/res/values-fr/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-fr/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sauvegarde complète" "Restauration complète" - "Vous avez demandé une sauvegarde complète de l\'ensemble des données vers un ordinateur de bureau connecté. Voulez-vous l\'autoriser ?"\n\n"Si vous n\'avez pas demandé la sauvegarde vous-même, n\'autorisez pas la poursuite de l\'opération." + "Vous avez demandé une sauvegarde complète de l\'ensemble des données vers un ordinateur de bureau connecté. Voulez-vous l\'autoriser ?\n\nSi vous n\'avez pas demandé la sauvegarde vous-même, n\'autorisez pas la poursuite de l\'opération." "Sauvegarder mes données" "Ne pas sauvegarder" - "Vous avez demandé une restauration complète de l\'ensemble des données à partir d\'un ordinateur de bureau connecté. Voulez-vous l\'autoriser ?"\n\n"Si vous n\'avez pas demandé vous-même la restauration, n\'autorisez pas sa poursuite. Cette opération remplacera toutes les données actuellement sur l\'appareil !" + "Vous avez demandé une restauration complète de l\'ensemble des données à partir d\'un ordinateur de bureau connecté. Voulez-vous l\'autoriser ?\n\nSi vous n\'avez pas demandé vous-même la restauration, n\'autorisez pas sa poursuite. Cette opération remplacera toutes les données actuellement sur l\'appareil !" "Restaurer mes données" "Ne pas restaurer" "Veuillez saisir votre mot de passe de sauvegarde actuel ci-dessous :" diff --git a/packages/BackupRestoreConfirmation/res/values-hi/strings.xml b/packages/BackupRestoreConfirmation/res/values-hi/strings.xml index 1495f8ed8d9d734bcef9a5b6ef58e208587fca40..dd0c64596edfa2c20a081c7bf6db8d5facd34629 100644 --- a/packages/BackupRestoreConfirmation/res/values-hi/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-hi/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "पूरà¥à¤£ बैकअप" "पूरà¥à¤£ पà¥à¤¨à¤°à¥à¤¸à¥â€à¤¥à¤¾à¤ªà¤¨à¤¾" - "कनेकà¥â€à¤Ÿ किâ€à¤ गठडेसà¥â€à¤•à¤Ÿà¥‰à¤ª कंपà¥â€à¤¯à¥‚टर से सभी डेटा के संपूरà¥à¤£ बैकअप का अनà¥à¤°à¥‹à¤§ किâ€à¤¯à¤¾ गया है. कà¥â€à¤¯à¤¾ आप इसकी अनà¥à¤®à¤¤à¤¿â€ देना चाहते हैं?"\n\n"यदि†आपने सà¥â€à¤µà¤¯à¤‚ बैकअप का अनà¥à¤°à¥‹à¤§ नहीं किâ€à¤¯à¤¾ है, तो पà¥à¤°à¤•à¥à¤°à¤¿â€à¤¯à¤¾ जारी रखने की अनà¥à¤®à¤¤à¤¿â€ न दें." + "कनेकà¥â€à¤Ÿ किâ€à¤ गठडेसà¥â€à¤•à¤Ÿà¥‰à¤ª कंपà¥â€à¤¯à¥‚टर से सभी डेटा के संपूरà¥à¤£ बैकअप का अनà¥à¤°à¥‹à¤§ किâ€à¤¯à¤¾ गया है. कà¥â€à¤¯à¤¾ आप इसकी अनà¥à¤®à¤¤à¤¿â€ देना चाहते हैं?\n\nयदि†आपने सà¥â€à¤µà¤¯à¤‚ बैकअप का अनà¥à¤°à¥‹à¤§ नहीं किâ€à¤¯à¤¾ है, तो पà¥à¤°à¤•à¥à¤°à¤¿â€à¤¯à¤¾ जारी रखने की अनà¥à¤®à¤¤à¤¿â€ न दें." "मेरे डेटा का बैकअप लें" "बैकअप न लें" - "कनेकà¥â€à¤Ÿ किâ€à¤ गठडेसà¥â€à¤•à¤Ÿà¥‰à¤ª कंपà¥â€à¤¯à¥‚टर से सभी डेटा की पूरà¥à¤£ पà¥à¤¨à¤°à¥à¤¸à¥à¤¥à¤¾à¤ªà¤¨à¤¾ का अनà¥à¤°à¥‹à¤§ किâ€à¤¯à¤¾ गया है. कà¥â€à¤¯à¤¾ आप इसकी अनà¥à¤®à¤¤à¤¿â€ देना चाहते हैं?"\n\n"यदि†आपने सà¥â€à¤µà¤¯à¤‚ पà¥à¤¨à¤°à¥à¤ªà¥à¤°à¤¾à¤ªà¥à¤¤à¤¿â€ का अनà¥à¤°à¥‹à¤§ नहीं किâ€à¤¯à¤¾ है, तो पà¥à¤°à¤•à¥à¤°à¤¿â€à¤¯à¤¾ जारी रखने की अनà¥à¤®à¤¤à¤¿â€ न दें. इससे वरà¥à¤¤à¤®à¤¾à¤¨ में आपके उपकरण पर मौजूद डेटा बदल जाà¤à¤—ा!" + "कनेकà¥â€à¤Ÿ किâ€à¤ गठडेसà¥â€à¤•à¤Ÿà¥‰à¤ª कंपà¥â€à¤¯à¥‚टर से सभी डेटा की पूरà¥à¤£ पà¥à¤¨à¤°à¥à¤¸à¥à¤¥à¤¾à¤ªà¤¨à¤¾ का अनà¥à¤°à¥‹à¤§ किâ€à¤¯à¤¾ गया है. कà¥â€à¤¯à¤¾ आप इसकी अनà¥à¤®à¤¤à¤¿â€ देना चाहते हैं?\n\nयदि†आपने सà¥â€à¤µà¤¯à¤‚ पà¥à¤¨à¤°à¥à¤ªà¥à¤°à¤¾à¤ªà¥à¤¤à¤¿â€ का अनà¥à¤°à¥‹à¤§ नहीं किâ€à¤¯à¤¾ है, तो पà¥à¤°à¤•à¥à¤°à¤¿â€à¤¯à¤¾ जारी रखने की अनà¥à¤®à¤¤à¤¿â€ न दें. इससे वरà¥à¤¤à¤®à¤¾à¤¨ में आपके उपकरण पर मौजूद डेटा बदल जाà¤à¤—ा!" "मेरा डेटा पà¥à¤¨à¤°à¥à¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ करें" "पà¥à¤¨à¤°à¥à¤¸à¥â€à¤¥à¤¾à¤ªà¤¿à¤¤ न करें" "कृपया नीचे अपना वरà¥à¤¤à¤®à¤¾à¤¨ बैकअप पासवरà¥à¤¡ डालें:" diff --git a/packages/BackupRestoreConfirmation/res/values-hr/strings.xml b/packages/BackupRestoreConfirmation/res/values-hr/strings.xml index c5c4ce98460acb4236980c10f310fa5e4e3cfa7f..3451103c48cbea5988ef7406979b2a6f7111593e 100644 --- a/packages/BackupRestoreConfirmation/res/values-hr/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-hr/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Puna sigurnosna kopija" "Potpuno vraćanje" - "Zatražena je potpuna sigurnosna kopija svih podataka na povezano stolno raÄunalo. Želite li to dozvoliti?"\n\n"Ako niste vi zatražili sigurnosnu kopiju, ne dozvolite nastavak te radnje." + "Zatražena je potpuna sigurnosna kopija svih podataka na povezano stolno raÄunalo. Želite li to dozvoliti?\n\nAko niste vi zatražili sigurnosnu kopiju, ne dozvolite nastavak te radnje." "Izradi sigurnosnu kopiju mojih podataka" "Ne radi sigurnosnu kopiju" - "Zatraženo je potpuno vraćanje svih podataka s povezanog stolnog raÄunala. Želite li to dozvoliti?"\n\n"Ako niste sami zatražili vraćanje, ne dozvolite nastavak radnje. To će zamijeniti sve podatke koji se trenutaÄno nalaze na ureÄ‘aju!" + "Zatraženo je potpuno vraćanje svih podataka s povezanog stolnog raÄunala. Želite li to dozvoliti?\n\nAko niste sami zatražili vraćanje, ne dozvolite nastavak radnje. To će zamijeniti sve podatke koji se trenutaÄno nalaze na ureÄ‘aju!" "Vrati moje podatke" "Ne vraćaj" "U nastavku unesite trenutaÄnu zaporku za sigurnosnu kopiju:" diff --git a/packages/BackupRestoreConfirmation/res/values-hu/strings.xml b/packages/BackupRestoreConfirmation/res/values-hu/strings.xml index c2901c1a02db10cd72b4c997e0e3413688d74d33..73d9a6380a3622dfa6af9fbc73cf95783daafc8a 100644 --- a/packages/BackupRestoreConfirmation/res/values-hu/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-hu/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Teljes biztonsági mentés" "Teljes helyreállítás" - "Kérés érkezett az összes adat biztonsági mentésére egy csatlakoztatott asztali számítógépre. Engedélyezi, hogy ez megtörténjen?"\n\n"Ha nem Ön kérte a mentést, ne engedélyezze a művelet folytatását." + "Kérés érkezett az összes adat biztonsági mentésére egy csatlakoztatott asztali számítógépre. Engedélyezi, hogy ez megtörténjen?\n\nHa nem Ön kérte a mentést, ne engedélyezze a művelet folytatását." "Adatok biztonsági mentése" "Ne mentsen" - "Kérés érkezett egy csatlakoztatott asztali számítógép összes adatának teljes helyreállítására. Engedélyezi, hogy ez megtörténjen?"\n\n"Ha nem Ön kérte a visszaállítást, ne engedélyezze a művelet folytatását. Ez az eszközön lévÅ‘ összes jelenlegi adatot felülírja!" + "Kérés érkezett egy csatlakoztatott asztali számítógép összes adatának teljes helyreállítására. Engedélyezi, hogy ez megtörténjen?\n\nHa nem Ön kérte a visszaállítást, ne engedélyezze a művelet folytatását. Ez az eszközön lévÅ‘ összes jelenlegi adatot felülírja!" "Adatok visszaállítása" "Ne állítsa vissza" "Kérjük, adja meg a jelenlegi biztonsági jelszót alább:" diff --git a/packages/BackupRestoreConfirmation/res/values-in/strings.xml b/packages/BackupRestoreConfirmation/res/values-in/strings.xml index 3fb6d6b729cba18db02a2779e9fdd633f30aaab0..f2d6ad51dfc191ddb54bb60dcc5e64dcd7637af4 100644 --- a/packages/BackupRestoreConfirmation/res/values-in/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-in/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Pencadangan sepenuhnya" "Pemulihan sepenuhnya" - "Cadangan lengkap semua data ke komputer yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini dilakukan?"\n\n"Jika Anda tidak meminta pencadangan ini, jangan izinkan operasi dilanjutkan." + "Cadangan lengkap semua data ke komputer yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini dilakukan?\n\nJika Anda tidak meminta pencadangan ini, jangan izinkan operasi dilanjutkan." "Cadangkan data saya" "Jangan mencadangkan" - "Pemulihan lengkap semua data dari komputer desktop yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini?"\n\n"Jika Anda tidak meminta pemulihan ini, jangan izinkan operasi dilanjutkan. Operasi ini akan mengganti data apa pun yang saat ini ada dalam perangkat!" + "Pemulihan lengkap semua data dari komputer desktop yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini?\n\nJika Anda tidak meminta pemulihan ini, jangan izinkan operasi dilanjutkan. Operasi ini akan mengganti data apa pun yang saat ini ada dalam perangkat!" "Memulihkan data saya" "Jangan pulihkan" "Masukkan sandi cadangan Anda saat ini di bawah:" diff --git a/packages/BackupRestoreConfirmation/res/values-it/strings.xml b/packages/BackupRestoreConfirmation/res/values-it/strings.xml index 9442ae350bb41606b640f44601972bc95891808e..a9e8ae46b46a3c3ef5e961dfac8cdfe6602e0a38 100644 --- a/packages/BackupRestoreConfirmation/res/values-it/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-it/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Backup completo" "Ripristino totale" - "È stato richiesto un backup completo di tutti i dati su un computer desktop connesso. Consentire l\'operazione?"\n\n"Se non hai richiesto il backup, non consentire all\'operazione di procedere." + "È stato richiesto un backup completo di tutti i dati su un computer desktop connesso. Consentire l\'operazione?\n\nSe non hai richiesto il backup, non consentire all\'operazione di procedere." "Effettua backup dei miei dati" "Non eseguire il backup" - "È stato richiesto un ripristino completo di tutti i dati da un computer desktop connesso. Consentire questa operazione?"\n\n"Se non hai richiesto il ripristino, non consentire all\'operazione di procedere. Questa operazione sostituirà tutti i dati attualmente presenti sul dispositivo." + "È stato richiesto un ripristino completo di tutti i dati da un computer desktop connesso. Consentire questa operazione?\n\nSe non hai richiesto il ripristino, non consentire all\'operazione di procedere. Questa operazione sostituirà tutti i dati attualmente presenti sul dispositivo." "Ripristina i miei dati" "Non ripristinare" "Inserisci la tua password di backup corrente di seguito:" diff --git a/packages/BackupRestoreConfirmation/res/values-iw/strings.xml b/packages/BackupRestoreConfirmation/res/values-iw/strings.xml index a41944a93fc537f01316a930f9dcc4d364b74be6..395c39ebd1fe8ab3d87a5fafbf37326e0ba08c04 100644 --- a/packages/BackupRestoreConfirmation/res/values-iw/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-iw/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "גיבוי מל×" "שחזור מל×" - "הוגשה בקשה לגיבוי ×ž×œ× ×©×œ כל ×”× ×ª×•× ×™× ×‘×ž×—×©×‘ שולחני מחובר. ×”×× ×תה רוצה ל×פשר פעולה זו? "\n\n"×× ×œ× ×‘×™×§×©×ª ×ת הגיבוי בעצמך, ×ל ת×פשר לפעולה להמשיך." + "הוגשה בקשה לגיבוי ×ž×œ× ×©×œ כל ×”× ×ª×•× ×™× ×‘×ž×—×©×‘ שולחני מחובר. ×”×× ×תה רוצה ל×פשר פעולה זו? \n\n×× ×œ× ×‘×™×§×©×ª ×ת הגיבוי בעצמך, ×ל ת×פשר לפעולה להמשיך." "גבה ×ת ×”× ×ª×•× ×™× ×©×œ×™" "×ל תגבה" - "הוגשה בקשה לשחזור ×ž×œ× ×©×œ כל ×”× ×ª×•× ×™× ×ž×ž×—×©×‘ שולחני מחובר. ×”×× ×תה רוצה ל×פשר פעולה זו? "\n" "\n" ×× ×œ× ×‘×™×§×©×ª ×ת השחזור בעצמך, ×ל ת×פשר לפעולה להמשיך. פעולה זו תחליף ×ת כל ×”× ×ª×•× ×™× ×©× ×ž×¦××™× ×›×¢×ª במכשיר!" + "הוגשה בקשה לשחזור ×ž×œ× ×©×œ כל ×”× ×ª×•× ×™× ×ž×ž×—×©×‘ שולחני מחובר. ×”×× ×תה רוצה ל×פשר פעולה זו? \n \n ×× ×œ× ×‘×™×§×©×ª ×ת השחזור בעצמך, ×ל ת×פשר לפעולה להמשיך. פעולה זו תחליף ×ת כל ×”× ×ª×•× ×™× ×©× ×ž×¦××™× ×›×¢×ª במכשיר!" "שחזר ×ת ×”× ×ª×•× ×™× ×©×œ×™" "×ל תשחזר" "הזן ×ת סיסמת הגיבוי הנוכחית למטה:" diff --git a/packages/BackupRestoreConfirmation/res/values-ja/strings.xml b/packages/BackupRestoreConfirmation/res/values-ja/strings.xml index 98916c5e24263a5203dfed6657c4f9a9d4f274e4..6859b3539d3b7b436231b4602d434ece4b423a23 100644 --- a/packages/BackupRestoreConfirmation/res/values-ja/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-ja/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "フルãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—" "完全ãªå¾©å…ƒ" - "接続ã—ã¦ã„るデスクトップパソコンã«å¯¾ã—ã¦ã™ã¹ã¦ã®ãƒ‡ãƒ¼ã‚¿ã®ãƒ•ãƒ«ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã‚’è¡Œã†ã‚ˆã†ãƒªã‚¯ã‚¨ã‚¹ãƒˆã•ã‚Œã¦ã„ã¾ã™ã€‚許å¯ã—ã¾ã™ã‹ï¼Ÿ"\n\n"ã”自分ã§ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—をリクエストã—ã¦ã„ãªã„å ´åˆã¯ã€ã“ã®æ“作ã®ç¶šè¡Œã‚’許å¯ã—ãªã„ã§ãã ã•ã„。" + "接続ã—ã¦ã„るデスクトップパソコンã«å¯¾ã—ã¦ã™ã¹ã¦ã®ãƒ‡ãƒ¼ã‚¿ã®ãƒ•ãƒ«ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã‚’è¡Œã†ã‚ˆã†ãƒªã‚¯ã‚¨ã‚¹ãƒˆã•ã‚Œã¦ã„ã¾ã™ã€‚許å¯ã—ã¾ã™ã‹ï¼Ÿ\n\nã”自分ã§ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—をリクエストã—ã¦ã„ãªã„å ´åˆã¯ã€ã“ã®æ“作ã®ç¶šè¡Œã‚’許å¯ã—ãªã„ã§ãã ã•ã„。" "データをãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—" "ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã—ãªã„" - "接続ã•ã‚Œã¦ã„るデスクトップパソコンã‹ã‚‰ã™ã¹ã¦ã®ãƒ‡ãƒ¼ã‚¿ã‚’完全ã«å¾©å…ƒã™ã‚‹ã‚ˆã†ã«ãƒªã‚¯ã‚¨ã‚¹ãƒˆã•ã‚Œã¦ã„ã¾ã™ã€‚許å¯ã—ã¾ã™ã‹ï¼Ÿ "\n\n"ã”自身ã§å¾©å…ƒã‚’リクエストã—ã¦ã„ãªã„å ´åˆã€æ“作ã®ç¶šè¡Œã‚’許å¯ã—ãªã„ã§ãã ã•ã„。端末ã«ç¾åœ¨ã‚るデータãŒã™ã¹ã¦ç½®æ›ã•ã‚Œã¾ã™ã€‚" + "接続ã•ã‚Œã¦ã„るデスクトップパソコンã‹ã‚‰ã™ã¹ã¦ã®ãƒ‡ãƒ¼ã‚¿ã‚’完全ã«å¾©å…ƒã™ã‚‹ã‚ˆã†ã«ãƒªã‚¯ã‚¨ã‚¹ãƒˆã•ã‚Œã¦ã„ã¾ã™ã€‚許å¯ã—ã¾ã™ã‹ï¼Ÿ \n\nã”自身ã§å¾©å…ƒã‚’リクエストã—ã¦ã„ãªã„å ´åˆã€æ“作ã®ç¶šè¡Œã‚’許å¯ã—ãªã„ã§ãã ã•ã„。端末ã«ç¾åœ¨ã‚るデータãŒã™ã¹ã¦ç½®æ›ã•ã‚Œã¾ã™ã€‚" "データを復元ã™ã‚‹" "復元ã—ãªã„" "以下ã«ç¾åœ¨ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—用ã®ãƒ‘スワードを入力ã—ã¦ãã ã•ã„:" diff --git a/packages/BackupRestoreConfirmation/res/values-ko/strings.xml b/packages/BackupRestoreConfirmation/res/values-ko/strings.xml index 4137058a31f4d20585b87f475fdc99c70843ccf4..23c8662436c1f6c925a6f29864a86c72d42a91d1 100644 --- a/packages/BackupRestoreConfirmation/res/values-ko/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-ko/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "ì „ì²´ 백업" "ì „ì²´ ë³µì›" - "ì—°ê²°ëœ ë°ìŠ¤í¬í†± ì»´í“¨í„°ì— ëŒ€í•œ ì „ì²´ ë°ì´í„° ë°±ì—…ì„ ìš”ì²­í–ˆìŠµë‹ˆë‹¤. ë°±ì—…ì„ ì‹¤í–‰í•˜ì‹œê² ìŠµë‹ˆê¹Œ?"\n\n"ì§ì ‘ ë°±ì—…ì„ ìš”ì²­í•œ ê²ƒì´ ì•„ë‹ˆë¼ë©´ ìž‘ì—…ì„ ì§„í–‰í•˜ì§€ 마시기 ë°”ëžë‹ˆë‹¤." + "ì—°ê²°ëœ ë°ìŠ¤í¬í†± ì»´í“¨í„°ì— ëŒ€í•œ ì „ì²´ ë°ì´í„° ë°±ì—…ì„ ìš”ì²­í–ˆìŠµë‹ˆë‹¤. ë°±ì—…ì„ ì‹¤í–‰í•˜ì‹œê² ìŠµë‹ˆê¹Œ?\n\nì§ì ‘ ë°±ì—…ì„ ìš”ì²­í•œ ê²ƒì´ ì•„ë‹ˆë¼ë©´ ìž‘ì—…ì„ ì§„í–‰í•˜ì§€ 마시기 ë°”ëžë‹ˆë‹¤." "ë°ì´í„° 백업" "백업하지 ì•ŠìŒ" - "ì—°ê²°ëœ ë°ìŠ¤í¬í†± ì»´í“¨í„°ì— ëŒ€í•œ ì „ì²´ ë°ì´í„° ë°±ì—…ì„ ìš”ì²­í–ˆìŠµë‹ˆë‹¤. ë°±ì—…ì„ ì‹¤í–‰í•˜ì‹œê² ìŠµë‹ˆê¹Œ?"\n\n"ì§ì ‘ ë°±ì—…ì„ ìš”ì²­í•œ ê²ƒì´ ì•„ë‹ˆë¼ë©´ ìž‘ì—…ì„ ì§„í–‰í•˜ì§€ 마시기 ë°”ëžë‹ˆë‹¤. ê¸°ê¸°ì— ìžˆëŠ” 모든 ë°ì´í„°ê°€ 변경ë©ë‹ˆë‹¤." + "ì—°ê²°ëœ ë°ìŠ¤í¬í†± ì»´í“¨í„°ì— ëŒ€í•œ ì „ì²´ ë°ì´í„° ë°±ì—…ì„ ìš”ì²­í–ˆìŠµë‹ˆë‹¤. ë°±ì—…ì„ ì‹¤í–‰í•˜ì‹œê² ìŠµë‹ˆê¹Œ?\n\nì§ì ‘ ë°±ì—…ì„ ìš”ì²­í•œ ê²ƒì´ ì•„ë‹ˆë¼ë©´ ìž‘ì—…ì„ ì§„í–‰í•˜ì§€ 마시기 ë°”ëžë‹ˆë‹¤. ê¸°ê¸°ì— ìžˆëŠ” 모든 ë°ì´í„°ê°€ 변경ë©ë‹ˆë‹¤." "ë°ì´í„° ë³µì›" "ë³µì›í•˜ì§€ ì•ŠìŒ" "ì•„ëž˜ì— í˜„ìž¬ 사용 ì¤‘ì¸ ë°±ì—… 비밀번호를 입력하세요." diff --git a/packages/BackupRestoreConfirmation/res/values-lt/strings.xml b/packages/BackupRestoreConfirmation/res/values-lt/strings.xml index 4e9efc56aff2431beb4e708d1a661eb4632f2f7f..44e67de79bae30db38a07b06f4599961f7a741ec 100644 --- a/packages/BackupRestoreConfirmation/res/values-lt/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-lt/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Visos atsarginÄ—s kopijos kÅ«rimas" "Visas atkÅ«rimas" - "Prijungtame staliniame kompiuteryje pageidauta sukurti visų duomenų atsarginÄ™ kopijÄ…. Ar norite, kad tai bÅ«tų atlikta?"\n\n"Jei patys atsarginÄ—s kopijos kurti nepraÅ¡Ä—te, neleiskite pradÄ—ti operacijos." + "Prijungtame staliniame kompiuteryje pageidauta sukurti visų duomenų atsarginÄ™ kopijÄ…. Ar norite, kad tai bÅ«tų atlikta?\n\nJei patys atsarginÄ—s kopijos kurti nepraÅ¡Ä—te, neleiskite pradÄ—ti operacijos." "Kurti atsarginÄ™ duomenų kopijÄ…" "Nekurti atsarginÄ—s kopijos" - "Pateikta visų staliniame kompiuteryje saugomų duomenų visiÅ¡ko atkÅ«rimo užklausa. Ar norite, kad tai bÅ«tų atlikta?"\n\n"Jei patys atkurti nepraÅ¡Ä—te, neleiskite pradÄ—ti operacijos. Kitaip bus pakeisti visi dabar įrenginyje saugomi duomenys!" + "Pateikta visų staliniame kompiuteryje saugomų duomenų visiÅ¡ko atkÅ«rimo užklausa. Ar norite, kad tai bÅ«tų atlikta?\n\nJei patys atkurti nepraÅ¡Ä—te, neleiskite pradÄ—ti operacijos. Kitaip bus pakeisti visi dabar įrenginyje saugomi duomenys!" "Atkurti mano duomenis" "Neatkurti" "Toliau įveskite dabartinį atsarginÄ—s kopijos slaptažodį:" diff --git a/packages/BackupRestoreConfirmation/res/values-lv/strings.xml b/packages/BackupRestoreConfirmation/res/values-lv/strings.xml index b8ce24b00750bd61fa49aa542d2785116220a53e..c58d6fde4dcd4e57a35bdddc7441cf42618fbf1e 100644 --- a/packages/BackupRestoreConfirmation/res/values-lv/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-lv/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Pilna dublÄ“Å¡ana" "Pilna atjaunoÅ¡ana" - "Ir pieprasÄ«ta visu datu pilnÄ«ga dublÄ“Å¡ana savienotÄ galda datorÄ. Vai vÄ“laties to atļaut?"\n\n"Ja neesat pieprasÄ«jis dublÄ“Å¡anu, neatļaujiet turpinÄt Å¡o darbÄ«bu." + "Ir pieprasÄ«ta visu datu pilnÄ«ga dublÄ“Å¡ana savienotÄ galda datorÄ. Vai vÄ“laties to atļaut?\n\nJa neesat pieprasÄ«jis dublÄ“Å¡anu, neatļaujiet turpinÄt Å¡o darbÄ«bu." "DublÄ“t manus datus" "Neveidot dublÄ“jumu" - "Ir pieprasÄ«ta visu savienotÄ galda datora datu pilnÄ«ga atjaunoÅ¡ana. Vai vÄ“laties to atļaut?"\n\n"Ja neesat pieprasÄ«jis atjaunoÅ¡anu, neatļaujiet turpinÄt Å¡o darbÄ«bu. TÄs rezultÄtÄ tiks aizstÄti visi paÅ¡reiz ierÄ«cÄ“ esoÅ¡ie dati!" + "Ir pieprasÄ«ta visu savienotÄ galda datora datu pilnÄ«ga atjaunoÅ¡ana. Vai vÄ“laties to atļaut?\n\nJa neesat pieprasÄ«jis atjaunoÅ¡anu, neatļaujiet turpinÄt Å¡o darbÄ«bu. TÄs rezultÄtÄ tiks aizstÄti visi paÅ¡reiz ierÄ«cÄ“ esoÅ¡ie dati!" "Atjaunot manus datus" "Neatjaunot" "LÅ«dzu, tÄlÄk ievadiet paÅ¡reizÄ“jo dublÄ“juma paroli:" diff --git a/packages/BackupRestoreConfirmation/res/values-ms/strings.xml b/packages/BackupRestoreConfirmation/res/values-ms/strings.xml index bcfa615825fa0c8d24fdc013592a2e2c4ea3bd7a..65a9ede7482d4b6d0def5729515c38ea0cc848f9 100644 --- a/packages/BackupRestoreConfirmation/res/values-ms/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-ms/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sandaran penuh" "Pemulihan penuh" - "Sandaran lengkap bagi semua data ke komputer meja yang bersambung telah diminta. Adakah anda mahu membenarkan ini berlaku?"\n\n"Jika anda tidak meminta sandaran ini sendiri, jangan benarkan operasi diteruskan." + "Sandaran lengkap bagi semua data ke komputer meja yang bersambung telah diminta. Adakah anda mahu membenarkan ini berlaku?\n\nJika anda tidak meminta sandaran ini sendiri, jangan benarkan operasi diteruskan." "Sandarkan data saya" "Jangan buat sandaran" - "Pemulihan penuh semua data dari komputer meja yang bersambung telah diminta. Adakah anda mahu membenarkan ini berlaku?"\n\n"Jika anda tidak meminta pemulihan ini sendiri, jangan benarkan operasi ini diteruskan. Ini akan menggantikan sebarang data semasa pada peranti!" + "Pemulihan penuh semua data dari komputer meja yang bersambung telah diminta. Adakah anda mahu membenarkan ini berlaku?\n\nJika anda tidak meminta pemulihan ini sendiri, jangan benarkan operasi ini diteruskan. Ini akan menggantikan sebarang data semasa pada peranti!" "Pulihkan data saya" "Jangan kembalikan" "Sila masukkan kata laluan sandaran semasa anda di bawah:" diff --git a/packages/BackupRestoreConfirmation/res/values-nb/strings.xml b/packages/BackupRestoreConfirmation/res/values-nb/strings.xml index a93c081f1182c409dbafbe523c96e5952255a8b3..d43ec2f25204dd9739d1272c963a02391d272420 100644 --- a/packages/BackupRestoreConfirmation/res/values-nb/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-nb/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fullstendig sikkerhetskopi" "Fullstendig gjenoppretting" - "En full sikkerhetskopi av alle dataene til en tilkoblet datamaskin er forespurt. Vil du tillate dette?"\n\n"Hvis du ikke har bedt om sikkerhetskopieringen selv, mÃ¥ du ikke tillate at operasjonen fortsetter." + "En full sikkerhetskopi av alle dataene til en tilkoblet datamaskin er forespurt. Vil du tillate dette?\n\nHvis du ikke har bedt om sikkerhetskopieringen selv, mÃ¥ du ikke tillate at operasjonen fortsetter." "Sikkerhetskopier dataene mine" "Ikke sikkerhetskopiér" - "En full gjenoppretting av alle data fra en tilkoblet datamaskin er forespurt. Vil du tillate dette?"\n\n"Hvis du ikke har bedt om gjenopprettingen selv, mÃ¥ du ikke la operasjonen fortsette. Handlingen vil erstatte alle dataene som ligger pÃ¥ enheten!" + "En full gjenoppretting av alle data fra en tilkoblet datamaskin er forespurt. Vil du tillate dette?\n\nHvis du ikke har bedt om gjenopprettingen selv, mÃ¥ du ikke la operasjonen fortsette. Handlingen vil erstatte alle dataene som ligger pÃ¥ enheten!" "Gjenopprett dataene mine" "Ikke gjenopprett" "Skriv inn ditt nÃ¥værende passord for sikkerhetskopiering nedenfor:" diff --git a/packages/BackupRestoreConfirmation/res/values-nl/strings.xml b/packages/BackupRestoreConfirmation/res/values-nl/strings.xml index d525429b546e4d3c09452f65b78838234b0c9214..1bf73e9320ed6d12695d981e81551efd5d9d4724 100644 --- a/packages/BackupRestoreConfirmation/res/values-nl/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-nl/strings.xml @@ -18,17 +18,17 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Volledige back-up" "Volledig herstel" - "Er is een volledige back-up van alle gegevens naar een verbonden desktopcomputer aangevraagd. Wilt u dit toestaan?"\n\n"Als u de back-up zelf niet heeft aangevraagd, moet u niet toestaan dat de bewerking wordt uitgevoerd." + "Er is een volledige back-up van alle gegevens naar een verbonden desktopcomputer aangevraagd. Wilt u dit toestaan?\n\nAls u de back-up zelf niet heeft aangevraagd, moet u niet toestaan dat de bewerking wordt uitgevoerd." "Back-up maken van mijn gegevens" "Geen back-up maken" - "Er is volledig herstel van alle gegevens van een verbonden desktopcomputer aangevraagd. Wilt u dit toestaan?"\n\n"Als u het herstel zelf niet heeft aangevraagd, moet u niet toestaan dat de bewerking wordt uitgevoerd. Bij herstel worden alle gegevens op het apparaat vervangen." + "Er is volledig herstel van alle gegevens van een verbonden desktopcomputer aangevraagd. Wilt u dit toestaan?\n\nAls u het herstel zelf niet heeft aangevraagd, moet u niet toestaan dat de bewerking wordt uitgevoerd. Bij herstel worden alle gegevens op het apparaat vervangen." "Mijn gegevens herstellen" "Niet herstellen" "Geef hieronder uw huidige back-upwachtwoord op:" "Geef hieronder uw wachtwoord voor apparaatcodering op." - "Geef hieronder uw wachtwoord voor apparaatcodering op. Dit wordt ook gebruikt om het back-uparchief te coderen." + "Geef hieronder uw wachtwoord voor apparaatversleuteling op. Dit wordt ook gebruikt om het back-uparchief te versleutelen." "Geef een wachtwoord op dat u wilt gebruiken voor het coderen van de gegevens van de volledige back-up. Als u dit leeg laat, wordt uw huidige back-upwachtwoord gebruikt:" - "Als u de gegevens van de volledige back-up wilt coderen, geeft u daarvoor hieronder een wachtwoord op:" + "Als u de gegevens van de volledige back-up wilt versleutelen, geeft u daarvoor hieronder een wachtwoord op:" "Als deze herstelgegevens zijn gecodeerd, geeft u hieronder het wachtwoord op:" "Back-up starten..." "Back-up voltooid" diff --git a/packages/BackupRestoreConfirmation/res/values-pl/strings.xml b/packages/BackupRestoreConfirmation/res/values-pl/strings.xml index 1a70bb08a06a969af3581d84d27266bee2f9f3d2..122b5dfd10ad47b9d042744ac7c9292297d1937d 100644 --- a/packages/BackupRestoreConfirmation/res/values-pl/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-pl/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "PeÅ‚na kopia zapasowa" "PeÅ‚ne przywracanie" - "Zażądano wykonania peÅ‚nej kopii zapasowej wszystkich danych na podÅ‚Ä…czonym komputerze stacjonarnym. Czy chcesz na to zezwolić?"\n\n"JeÅ›li żądanie utworzenia kopii zapasowej nie pochodzi od Ciebie, nie zezwalaj na kontynuowanie tej operacji." + "Zażądano wykonania peÅ‚nej kopii zapasowej wszystkich danych na podÅ‚Ä…czonym komputerze stacjonarnym. Czy chcesz na to zezwolić?\n\nJeÅ›li żądanie utworzenia kopii zapasowej nie pochodzi od Ciebie, nie zezwalaj na kontynuowanie tej operacji." "Utwórz kopiÄ™ zapasowÄ… danych" "Nie twórz kopii zapasowej" - "Zażądano peÅ‚nego przywrócenia wszystkich danych z poÅ‚Ä…czonego komputera stacjonarnego. Czy chcesz na to zezwolić?"\n\n"JeÅ›li żądanie przywrócenia nie pochodzi od Ciebie, nie zezwalaj na kontynuowanie tej operacji. Spowoduje to zastÄ…pienie wszelkich danych znajdujÄ…cych siÄ™ aktualnie w urzÄ…dzeniu." + "Zażądano peÅ‚nego przywrócenia wszystkich danych z poÅ‚Ä…czonego komputera stacjonarnego. Czy chcesz na to zezwolić?\n\nJeÅ›li żądanie przywrócenia nie pochodzi od Ciebie, nie zezwalaj na kontynuowanie tej operacji. Spowoduje to zastÄ…pienie wszelkich danych znajdujÄ…cych siÄ™ aktualnie w urzÄ…dzeniu." "Przywróć moje dane" "Nie przywracaj" "Wpisz poniżej aktualne hasÅ‚o kopii zapasowej:" diff --git a/packages/BackupRestoreConfirmation/res/values-pt-rPT/strings.xml b/packages/BackupRestoreConfirmation/res/values-pt-rPT/strings.xml index a486f5487eb5601b5e92787ffdff3fecc28c7b3a..477d4230cae3f0beead67c0f3eb4fdead5d205bb 100644 --- a/packages/BackupRestoreConfirmation/res/values-pt-rPT/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-pt-rPT/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Cópia de segurança completa" "Restauro completo" - "Foi solicitada uma cópia de segurança completa de todos os dados para um computador. Pretende permitir esta operação?"\n\n"Caso não tenha solicitado a cópia de segurança, não permita que a operação prossiga." + "Foi solicitada uma cópia de segurança completa de todos os dados para um computador. Pretende permitir esta operação?\n\nCaso não tenha solicitado a cópia de segurança, não permita que a operação prossiga." "Fazer cópia de seg. dos dados" "Não efetuar cópia de seg." - "Foi solicitado um restauro completo de todos os dados a partir de um computador. Pretende permitir esta operação?"\n\n"Caso não tenha solicitado o restauro, não permita que a operação prossiga. Isto substituirá os dados existentes no equipamento!" + "Foi solicitado um restauro completo de todos os dados a partir de um computador. Pretende permitir esta operação?\n\nCaso não tenha solicitado o restauro, não permita que a operação prossiga. Isto substituirá os dados existentes no equipamento!" "Restaurar os meus dados" "Não restaurar" "Introduza a palavra-passe de cópia de segurança atual abaixo:" diff --git a/packages/BackupRestoreConfirmation/res/values-pt/strings.xml b/packages/BackupRestoreConfirmation/res/values-pt/strings.xml index 99fd2e114b08529b80975a4f4c3f5770973d25e9..a56b31ca902d20a6c09d8dde5b8348ad5a6fa467 100644 --- a/packages/BackupRestoreConfirmation/res/values-pt/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-pt/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Backup completo" "Restauração completa" - "Foi solicitado um backup completo de todos os dados para um computador conectado. Deseja permitir que isso aconteça?"\n\n"Caso você não tenha solicitado o backup, não permita que a operação prossiga." + "Foi solicitado um backup completo de todos os dados para um computador conectado. Deseja permitir que isso aconteça?\n\nCaso você não tenha solicitado o backup, não permita que a operação prossiga." "Fazer backup de meus dados" "Não fazer backup" - "Foi solicitada uma restauração completa de todos os dados de um computador conectado. Deseja permitir que isso ocorra?"\n\n"Caso você não tenha solicitado a restauração, não permita que a operação prossiga. Isso substituirá todos os dados existentes no dispositivo!" + "Foi solicitada uma restauração completa de todos os dados de um computador conectado. Deseja permitir que isso ocorra?\n\nCaso você não tenha solicitado a restauração, não permita que a operação prossiga. Isso substituirá todos os dados existentes no dispositivo!" "Restaurar meus dados" "Não restaurar" "Digite sua senha de backup atual abaixo:" diff --git a/packages/BackupRestoreConfirmation/res/values-ro/strings.xml b/packages/BackupRestoreConfirmation/res/values-ro/strings.xml index 4c49bf8d47e1651d27a89d97bf42f8c5b736fc56..839edbbc2ff785c7a662f2311e822451b7e182f7 100644 --- a/packages/BackupRestoreConfirmation/res/values-ro/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-ro/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Copiere de rezervă completă" "Restabilire completă" - "S-a solicitat crearea unei copii de rezervă complete a tuturor datelor pe un computer desktop conectat. DoriÅ£i să permiteÅ£i acest lucru?"\n\n"Dacă nu aÅ£i solicitat dvs. copierea de rezervă, nu permiteÅ£i ca operaÅ£iunea să continue." + "S-a solicitat crearea unei copii de rezervă complete a tuturor datelor pe un computer desktop conectat. DoriÅ£i să permiteÅ£i acest lucru?\n\nDacă nu aÅ£i solicitat dvs. copierea de rezervă, nu permiteÅ£i ca operaÅ£iunea să continue." "CreaÅ£i copii de rezervă pentru datele dvs." "Nu creaÅ£i copii de rezervă" - "S-a solicitat o restabilire completă a tuturor datelor de pe un computer desktop conectat. DoriÅ£i să permiteÅ£i acest lucru?"\n\n"Dacă nu dvs. aÅ£i solicitat această restabilire, nu permiteÅ£i continuarea operaÅ£iunii. Acest proces va înlocui toate datele existente în prezent pe dispozitiv!" + "S-a solicitat o restabilire completă a tuturor datelor de pe un computer desktop conectat. DoriÅ£i să permiteÅ£i acest lucru?\n\nDacă nu dvs. aÅ£i solicitat această restabilire, nu permiteÅ£i continuarea operaÅ£iunii. Acest proces va înlocui toate datele existente în prezent pe dispozitiv!" "RestabiliÅ£i datele dvs." "Nu restabiliÅ£i" "IntroduceÅ£i mai jos parola actuală pentru copia de rezervă:" diff --git a/packages/BackupRestoreConfirmation/res/values-ru/strings.xml b/packages/BackupRestoreConfirmation/res/values-ru/strings.xml index 0dbba0527c9403736d256fc4666f126b312e24fa..f516493899d80c11c324b2520c05a3bbcd997338 100644 --- a/packages/BackupRestoreConfirmation/res/values-ru/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-ru/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Полное резервное копирование" "Полное воÑÑтановление" - "Запрошено резервное копирование вÑех данных на подключенном компьютере. Разрешить?"\n\n"ЕÑли вы не запрашивали Ñтого, не разрешайте выполнение операции." + "Запрошено резервное копирование вÑех данных на подключенном компьютере. Разрешить?\n\nЕÑли вы не запрашивали Ñтого, не разрешайте выполнение операции." "Создать резервную копию данных" "Ðе Ñоздавать резервную копию" - "Подключенный компьютер запрашивает воÑÑтановление вÑех данных. Разрешить?"\n\n"ЕÑли вы не запрашивали Ñтого, не разрешайте выполнение операции, Ñ‚. к. она заменит вÑе данные на Ñтом уÑтройÑтве." + "Подключенный компьютер запрашивает воÑÑтановление вÑех данных. Разрешить?\n\nЕÑли вы не запрашивали Ñтого, не разрешайте выполнение операции, Ñ‚. к. она заменит вÑе данные на Ñтом уÑтройÑтве." "ВоÑÑтановить данные" "Ðе воÑÑтанавливать" "Введите пароль Ð´Ð»Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð³Ð¾ копированиÑ:" diff --git a/packages/BackupRestoreConfirmation/res/values-sk/strings.xml b/packages/BackupRestoreConfirmation/res/values-sk/strings.xml index b4321657a91a48441e73ae5c320f730ff62e88a9..21e21b53e34309b4d983ddbcff9b20798774cd19 100644 --- a/packages/BackupRestoreConfirmation/res/values-sk/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-sk/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Úplná záloha" "Úplné obnovenie" - "Bola vyžiadaná úplná záloha vÅ¡etkých dát do pripojeného poÄítaÄa. Chcete túto akciu povoliÅ¥?"\n\n"Ak ste zálohu nevyžiadali vy, túto operáciu nepovoľujte." + "Bola vyžiadaná úplná záloha vÅ¡etkých dát do pripojeného poÄítaÄa. Chcete túto akciu povoliÅ¥?\n\nAk ste zálohu nevyžiadali vy, túto operáciu nepovoľujte." "ZálohovaÅ¥ údaje" "NezálohovaÅ¥" - "Z pripojeného poÄítaÄa bolo vyžiadané úplné obnovenie vÅ¡etkých údajov. Chcete túto akciu povoliÅ¥?"\n\n"Ak ste toto obnovenie nevyžiadali vy, túto operáciu nepovoľujte. Táto akcia nahradí vÅ¡etky údaje v zariadení." + "Z pripojeného poÄítaÄa bolo vyžiadané úplné obnovenie vÅ¡etkých údajov. Chcete túto akciu povoliÅ¥?\n\nAk ste toto obnovenie nevyžiadali vy, túto operáciu nepovoľujte. Táto akcia nahradí vÅ¡etky údaje v zariadení." "ObnoviÅ¥ údaje" "NeobnoviÅ¥" "Zadajte svoje aktuálne heslo pre zálohu nižšie:" diff --git a/packages/BackupRestoreConfirmation/res/values-sl/strings.xml b/packages/BackupRestoreConfirmation/res/values-sl/strings.xml index 5df044914726aa7d0a93c103acd90192fc6c88b6..17d4d8043882271d15acdfc9cd365ec99bcdcc80 100644 --- a/packages/BackupRestoreConfirmation/res/values-sl/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-sl/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Polna varnostna kopija" "Popolna obnova" - "Zahtevano je popolno varnostno kopiranje vseh podatkov v povezanem raÄunalniku. Ali želite to dovoliti?"\n\n"ÄŒe varnostnega kopiranja niste zahtevali, ne dovolite nadaljevanja postopka." + "Zahtevano je popolno varnostno kopiranje vseh podatkov v povezanem raÄunalniku. Ali želite to dovoliti?\n\nÄŒe varnostnega kopiranja niste zahtevali, ne dovolite nadaljevanja postopka." "Varnostno kopiraj moje podatke" "Brez varnostnega kopiranja" - "Zahtevana je popolna obnovitev vseh podatkov iz povezanega raÄunalnika. Ali želite to dovoliti?"\n\n"ÄŒe niste zahtevali obnovitve, ne dovolite nadaljevanja postopka. Sicer bodo zamenjani vsi podatki, trenutno shranjeni v napravi." + "Zahtevana je popolna obnovitev vseh podatkov iz povezanega raÄunalnika. Ali želite to dovoliti?\n\nÄŒe niste zahtevali obnovitve, ne dovolite nadaljevanja postopka. Sicer bodo zamenjani vsi podatki, trenutno shranjeni v napravi." "Obnovi moje podatke" "Ne obnovi" "Spodaj vnesite trenutno geslo za varnostno kopiranje:" diff --git a/packages/BackupRestoreConfirmation/res/values-sr/strings.xml b/packages/BackupRestoreConfirmation/res/values-sr/strings.xml index 0a5859eec923bac0834c6759cff22016ce6fc902..82cb85f3cc94cf9a1a9e9d7a209cb6eb7605e60d 100644 --- a/packages/BackupRestoreConfirmation/res/values-sr/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-sr/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Прављење резервне копије Ñвих података" "Потпуно враћање" - "Захтевана је потпуна резервна копија Ñвих података на повезани Ñтони рачунар. Да ли желите да дозволите то?"\n\n"Ðко ниÑте лично захтевали резервну копију, не дозвољавајте наÑтавак радње." + "Захтевана је потпуна резервна копија Ñвих података на повезани Ñтони рачунар. Да ли желите да дозволите то?\n\nÐко ниÑте лично захтевали резервну копију, не дозвољавајте наÑтавак радње." "Ðаправи резервну копију мојих података" "Ðе прави резервне копије" - "Захтевано је потпуно враћање Ñвих података Ñа повезаног Ñтоног рачунара. Да ли желите да дозволите то?"\n\n"Ðко ниÑте лично захтевали враћање, не дозвољавајте наÑтавак радње. Тиме ћете заменити Ñве податке који Ñу тренутно на уређају!" + "Захтевано је потпуно враћање Ñвих података Ñа повезаног Ñтоног рачунара. Да ли желите да дозволите то?\n\nÐко ниÑте лично захтевали враћање, не дозвољавајте наÑтавак радње. Тиме ћете заменити Ñве податке који Ñу тренутно на уређају!" "Врати моје податке" "Ðе враћај" "УнеÑите тренутну лозинку резервне копије у наÑтавку:" diff --git a/packages/BackupRestoreConfirmation/res/values-sv/strings.xml b/packages/BackupRestoreConfirmation/res/values-sv/strings.xml index 167fce31e02f620b8550d9162bc0ebcbd92ef39a..a2ef430fa66d88c14f21d2b05191f5b36de0db92 100644 --- a/packages/BackupRestoreConfirmation/res/values-sv/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-sv/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fullständig säkerhetskopiering" "Fullständig Ã¥terställning" - "En fullständig säkerhetskopia av alla data till en ansluten dator har begärts. Vill du tillÃ¥ta detta?"\n\n"Om du inte själv begärde säkerhetskopian ska du inte tillÃ¥ta detta." + "En fullständig säkerhetskopia av alla data till en ansluten dator har begärts. Vill du tillÃ¥ta detta?\n\nOm du inte själv begärde säkerhetskopian ska du inte tillÃ¥ta detta." "Säkerhetskopiera mina data" "Säkerhetskopiera inte" - "En fullständig Ã¥terställning av alla data frÃ¥n en ansluten dator har begärts. Vill du tillÃ¥ta detta? "\n" "\n" Om du inte själv har begärt Ã¥terställningen ska du inte tillÃ¥ta den. Alla data som finns pÃ¥ enheten kommer dÃ¥ att ersättas!" + "En fullständig Ã¥terställning av alla data frÃ¥n en ansluten dator har begärts. Vill du tillÃ¥ta detta? \n \n Om du inte själv har begärt Ã¥terställningen ska du inte tillÃ¥ta den. Alla data som finns pÃ¥ enheten kommer dÃ¥ att ersättas!" "Ã…terställ mina data" "Ã…terställ inte" "Ange det aktuella lösenordet för säkerhetskopian nedan:" diff --git a/packages/BackupRestoreConfirmation/res/values-sw/strings.xml b/packages/BackupRestoreConfirmation/res/values-sw/strings.xml index 493c168ea75ecc8b40f42de513ca387cbd1b8ed3..619a6db529e4b343dc5480203ba3901414006fc4 100644 --- a/packages/BackupRestoreConfirmation/res/values-sw/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-sw/strings.xml @@ -17,11 +17,11 @@ "Kuhifadhi kikamilifu" - "Kurejeza kamili" - "Chelezo kamili la data iliyounganishwa kwenye eneo kazi la kompyuta limeombwa. Unataka kuruhusu hii kutendeka?"\n\n" Ikiwa hukuomba chelezo mwenyewe, usikubali uendeshaji kuendelea." - "Cheleza data yangu" + "Kurejesha kila kitu" + "Chelezo kamili la data iliyounganishwa kwenye eneo kazi la kompyuta limeombwa. Unataka kuruhusu hii kutendeka?\n\n Ikiwa hukuomba chelezo mwenyewe, usikubali uendeshaji kuendelea." + "Hifadhi nakala ya data yangu" "Usicheleze" - "Kurejesha kamili kwa data nzima kutoka kwa eneo kazi la kompyuta lililounganishwa limeombwa. Unataka kuruhusu hii kutendeka?"\n\n" Ikiwa hukuweza kurejesha upya mwenyewe, usiruhusu uendeshaji huu kuendelea. Hii itaweka upya data yoyote iliyo kwenye kifaa hiki sasa!" + "Kurejesha kamili kwa data nzima kutoka kwa eneo kazi la kompyuta lililounganishwa limeombwa. Unataka kuruhusu hii kutendeka?\n\n Ikiwa hukuweza kurejesha upya mwenyewe, usiruhusu uendeshaji huu kuendelea. Hii itaweka upya data yoyote iliyo kwenye kifaa hiki sasa!" "Rejesha upya data yangu" "Usirejeshe upya" "Tafadhali ingiza nenosiri lako la chelezo hapo chini:" @@ -32,7 +32,7 @@ "Ikiwa data iliyorejeshwa upya, tafadhali ingiza nenosiri lililo hapo chini:" "Inaanza kuhifadhi..." "Imemaliza kuhifadhi" - "Inaanza kurejeza..." - "Kurejeza kumekamilika" + "Inaanza kurejesha..." + "Kurejesha kumekamilika" "Muda wa uendeshaji umeisha" diff --git a/packages/BackupRestoreConfirmation/res/values-th/strings.xml b/packages/BackupRestoreConfirmation/res/values-th/strings.xml index 2d08620014553ac8d6b3c7f4eb856a3d79961fbe..c0543a026dc50b1e6df77d72ec4288a3dc2692e1 100644 --- a/packages/BackupRestoreConfirmation/res/values-th/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-th/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "à¸à¸²à¸£à¸ªà¸³à¸£à¸­à¸‡à¸‚้อมูลทั้งหมด" "à¸à¸²à¸£à¸„ืนค่าทั้งหมด" - "เราได้รับà¸à¸²à¸£à¸‚อให้ทำà¸à¸²à¸£à¸ªà¸³à¸£à¸­à¸‡à¸‚้อมูลทั้งหมดลงในคอมพิวเตอร์เดสà¸à¹Œà¸—็อปที่เชื่อมต่ออยู่ คุณต้องà¸à¸²à¸£à¸­à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£à¸•à¸²à¸¡à¸™à¸µà¹‰à¸«à¸£à¸·à¸­à¹„ม่"\n\n" หาà¸à¸„ุณไม่ได้เป็นผู้ขอให้ทำà¸à¸²à¸£à¸ªà¸³à¸£à¸­à¸‡à¸‚้อมูลดังà¸à¸¥à¹ˆà¸²à¸§ โปรดอย่าอนุà¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£" + "เราได้รับà¸à¸²à¸£à¸‚อให้ทำà¸à¸²à¸£à¸ªà¸³à¸£à¸­à¸‡à¸‚้อมูลทั้งหมดลงในคอมพิวเตอร์เดสà¸à¹Œà¸—็อปที่เชื่อมต่ออยู่ คุณต้องà¸à¸²à¸£à¸­à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£à¸•à¸²à¸¡à¸™à¸µà¹‰à¸«à¸£à¸·à¸­à¹„ม่\n\n หาà¸à¸„ุณไม่ได้เป็นผู้ขอให้ทำà¸à¸²à¸£à¸ªà¸³à¸£à¸­à¸‡à¸‚้อมูลดังà¸à¸¥à¹ˆà¸²à¸§ โปรดอย่าอนุà¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£" "สำรองข้อมูลของฉัน" "ไม่ต้องสำรองข้อมูล" - "เราได้รับà¸à¸²à¸£à¸‚อให้ทำà¸à¸²à¸£à¸„ืนค่าข้อมูลทั้งหมดจาà¸à¸„อมพิวเตอร์เดสà¸à¹Œà¸—็อปที่เชื่อมต่ออยู่ คุณต้องà¸à¸²à¸£à¸­à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£à¸•à¸²à¸¡à¸™à¸µà¹‰à¸«à¸£à¸·à¸­à¹„ม่"\n\n" หาà¸à¸„ุณไม่ได้เป็นผู้ขอให้ทำà¸à¸²à¸£à¸„ืนค่าดังà¸à¸¥à¹ˆà¸²à¸§ โปรดอย่าอนุà¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£ à¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¸™à¸µà¹‰à¸ˆà¸°à¹à¸—นค่าข้อมูลปัจจุบันทั้งหมดในอุปà¸à¸£à¸“์" + "เราได้รับà¸à¸²à¸£à¸‚อให้ทำà¸à¸²à¸£à¸„ืนค่าข้อมูลทั้งหมดจาà¸à¸„อมพิวเตอร์เดสà¸à¹Œà¸—็อปที่เชื่อมต่ออยู่ คุณต้องà¸à¸²à¸£à¸­à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£à¸•à¸²à¸¡à¸™à¸µà¹‰à¸«à¸£à¸·à¸­à¹„ม่\n\n หาà¸à¸„ุณไม่ได้เป็นผู้ขอให้ทำà¸à¸²à¸£à¸„ืนค่าดังà¸à¸¥à¹ˆà¸²à¸§ โปรดอย่าอนุà¸à¸²à¸•à¹ƒà¸«à¹‰à¸”ำเนินà¸à¸²à¸£ à¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¸™à¸µà¹‰à¸ˆà¸°à¹à¸—นค่าข้อมูลปัจจุบันทั้งหมดในอุปà¸à¸£à¸“์" "คืนค่าข้อมูลของฉัน" "ไม่คืนค่า" "โปรดป้อนรหัสผ่านà¸à¸²à¸£à¸ªà¸³à¸£à¸­à¸‡à¸‚้อมูลปัจจุบันของคุณด้านล่างนี้:" diff --git a/packages/BackupRestoreConfirmation/res/values-tl/strings.xml b/packages/BackupRestoreConfirmation/res/values-tl/strings.xml index 97662b5af3e7e5e193741a7f0a02da0c46eeb52d..5c564bafc18f24f240b92f728749e919098c93dc 100644 --- a/packages/BackupRestoreConfirmation/res/values-tl/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-tl/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Ganap na pag-backup" "Ganap na pagpapanumbalik" - "Hiniling ang isang buong pag-backup ng lahat ng data sa isang nakakonektang desktop computer. Gusto mo ba itong payagang maganap? "\n\n"Kung hindi ikaw mismo ang humiling ng pag-backup, huwag payagang magpatuloy ang pagpapatakbo." + "Hiniling ang isang buong pag-backup ng lahat ng data sa isang nakakonektang desktop computer. Gusto mo ba itong payagang maganap? \n\nKung hindi ikaw mismo ang humiling ng pag-backup, huwag payagang magpatuloy ang pagpapatakbo." "I-back up ang aking data" "Huwag i-back up" - "Hiniling ang isang buong pagpapanumbalik ng lahat ng data mula sa isang nakakonektang desktop computer. Gusto mo ba itong payagang maganap?"\n\n"Kung hindi ikaw mismo ang humiling ng pagpapanumbalik, huwag payagang magpatuloy ang pagpapatakbo. Papalitan nito ang anumang data na kasalukuyang nasa device!" + "Hiniling ang isang buong pagpapanumbalik ng lahat ng data mula sa isang nakakonektang desktop computer. Gusto mo ba itong payagang maganap?\n\nKung hindi ikaw mismo ang humiling ng pagpapanumbalik, huwag payagang magpatuloy ang pagpapatakbo. Papalitan nito ang anumang data na kasalukuyang nasa device!" "Ipanumbalik ang aking data" "Huwag ipanumbalik" "Pakilagay ang iyong kasalukuyang backup na password sa ibaba:" diff --git a/packages/BackupRestoreConfirmation/res/values-tr/strings.xml b/packages/BackupRestoreConfirmation/res/values-tr/strings.xml index 62b9f4be0501b610eb7aefe58b08752eaac56f8c..591be7c63fd811467127fb8d3f81822df5f4a445 100644 --- a/packages/BackupRestoreConfirmation/res/values-tr/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-tr/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Tam yedekleme" "Tam geri yükleme" - "Tüm verilerin baÄŸlı bir masaüstü bilgisayara tam olarak yedeklenmesi için istekte bulunuldu?"\n\n"Yedekleme isteÄŸinde siz bulunmadıysanız, iÅŸlemin devam etmesine izin vermeyin." + "Tüm verilerin baÄŸlı bir masaüstü bilgisayara tam olarak yedeklenmesi için istekte bulunuldu?\n\nYedekleme isteÄŸinde siz bulunmadıysanız, iÅŸlemin devam etmesine izin vermeyin." "Verilerimi yedekle" "Yedekleme" - "Tüm verilerin, baÄŸlı bir masaüstü bilgisayardan tam olarak geri yüklenmesi için istekte bulunuldu. Bu iÅŸleme izin vermek istiyor musunuz?"\n\n"Geri yükleme isteÄŸinde siz bulunmadıysanız, iÅŸlemin ilerlemesine izin vermeyin. Bu iÅŸlem, cihazınızdaki tüm verileri silip üzerine yazar!" + "Tüm verilerin, baÄŸlı bir masaüstü bilgisayardan tam olarak geri yüklenmesi için istekte bulunuldu. Bu iÅŸleme izin vermek istiyor musunuz?\n\nGeri yükleme isteÄŸinde siz bulunmadıysanız, iÅŸlemin ilerlemesine izin vermeyin. Bu iÅŸlem, cihazınızdaki tüm verileri silip üzerine yazar!" "Verilerimi geri yükle" "Geri yükleme" "Lütfen mevcut yedekleme ÅŸifrenizi aÅŸağıya girin:" diff --git a/packages/BackupRestoreConfirmation/res/values-uk/strings.xml b/packages/BackupRestoreConfirmation/res/values-uk/strings.xml index f3dfa1ceb5e6e6248ea33d406ab388da40982f0a..b4ddef1ed2ce02a7d36cdcba746e999eb49972ff 100644 --- a/packages/BackupRestoreConfirmation/res/values-uk/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-uk/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Повне резервне копіюваннÑ" "Повне відновленнÑ" - "Ðадійшов запит на повне резервне ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ð²ÑÑ–Ñ… даних на під’єднаний наÑтільний комп’ютер. Дозволити це?"\n\n"Якщо ви не надÑилали запит на резервне копіюваннÑ, не дозволÑйте виконувати цю операцію." + "Ðадійшов запит на повне резервне ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ð²ÑÑ–Ñ… даних на під’єднаний наÑтільний комп’ютер. Дозволити це?\n\nЯкщо ви не надÑилали запит на резервне копіюваннÑ, не дозволÑйте виконувати цю операцію." "Резервне ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ…" "Ðе Ñтворювати резервну копію" - "Ðадійшов запит на повне Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… даних із під’єднаного наÑтільного комп’ютера. Дозволити це?"\n\n"Якщо ви не надÑилали запит на відновленнÑ, не дозволÑйте виконувати цю операцію. УÑÑ– розміщені зараз на приÑтрої дані буде замінено!" + "Ðадійшов запит на повне Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… даних із під’єднаного наÑтільного комп’ютера. Дозволити це?\n\nЯкщо ви не надÑилали запит на відновленнÑ, не дозволÑйте виконувати цю операцію. УÑÑ– розміщені зараз на приÑтрої дані буде замінено!" "Відновити мої дані" "Ðе відновлювати" "Введіть Ñвій поточний пароль резервного ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ð½Ð¸Ð¶Ñ‡Ðµ:" diff --git a/packages/BackupRestoreConfirmation/res/values-vi/strings.xml b/packages/BackupRestoreConfirmation/res/values-vi/strings.xml index ac34a993432ad149bdb60070a3dbd5bcd3a13367..69e8f9c57cd73a9a82b9b1902549bc1057527343 100644 --- a/packages/BackupRestoreConfirmation/res/values-vi/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-vi/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sao lÆ°u hoàn toàn" "Khôi phục hoàn toàn" - "Äã yêu cầu sao lÆ°u đầy đủ toàn bá»™ dữ liệu tá»›i máy tính được kết nối. Bạn có muốn cho phép Ä‘iá»u này xảy ra không?"\n\n"Nếu không phải bản thân bạn yêu cầu sao lÆ°u, đừng cho phép thao tác này tiếp tục." + "Äã yêu cầu sao lÆ°u đầy đủ toàn bá»™ dữ liệu tá»›i máy tính được kết nối. Bạn có muốn cho phép Ä‘iá»u này xảy ra không?\n\nNếu không phải bản thân bạn yêu cầu sao lÆ°u, đừng cho phép thao tác này tiếp tục." "Sao lÆ°u dữ liệu của tôi" "Không sao lÆ°u" - "Äã yêu cầu khôi phục đầy đủ toàn bá»™ dữ liệu từ máy tính được kết nối. Bạn có muốn cho phép Ä‘iá»u này xảy ra không?"\n\n"Nếu không phải bản thân bạn yêu cầu khôi phục, đừng cho phép thao tác này tiếp tục. Thao tác này sẽ thay thế má»i dữ liệu hiện tại trên thiết bị!" + "Äã yêu cầu khôi phục đầy đủ toàn bá»™ dữ liệu từ máy tính được kết nối. Bạn có muốn cho phép Ä‘iá»u này xảy ra không?\n\nNếu không phải bản thân bạn yêu cầu khôi phục, đừng cho phép thao tác này tiếp tục. Thao tác này sẽ thay thế má»i dữ liệu hiện tại trên thiết bị!" "Khôi phục dữ liệu của tôi" "Không khôi phục" "Vui lòng nhập mật khẩu sao lÆ°u hiện tại của bạn bên dÆ°á»›i:" diff --git a/packages/BackupRestoreConfirmation/res/values-zh-rCN/strings.xml b/packages/BackupRestoreConfirmation/res/values-zh-rCN/strings.xml index 5f3ca05c00590df3ddf6cba145e771a357df8426..b2764fbbe6ebde518c7293947a60301f314bee5a 100644 --- a/packages/BackupRestoreConfirmation/res/values-zh-rCN/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-zh-rCN/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "完全备份" "完全还原" - "系统请求将所有数æ®å®Œæ•´å¤‡ä»½è‡³å·²è¿žæŽ¥çš„æ¡Œé¢è®¡ç®—机。å…许此æ“作å—?"\n\n"如果您本人未è¦æ±‚备份,请阻止该æ“作。" + "系统请求将所有数æ®å®Œæ•´å¤‡ä»½è‡³å·²è¿žæŽ¥çš„æ¡Œé¢è®¡ç®—机。å…许此æ“作å—?\n\n如果您本人未è¦æ±‚备份,请阻止该æ“作。" "备份我的数æ®" "ä¸å¤‡ä»½" - "系统请求从连接的桌é¢è®¡ç®—机完整还原所有数æ®ã€‚å…许此æ“作å—?"\n\n"如果您本人未è¦æ±‚还原,请阻止该æ“作。该æ“作会覆盖设备上当å‰çš„所有数æ®ï¼" + "系统请求从连接的桌é¢è®¡ç®—机完整还原所有数æ®ã€‚å…许此æ“作å—?\n\n如果您本人未è¦æ±‚还原,请阻止该æ“作。该æ“作会覆盖设备上当å‰çš„所有数æ®ï¼" "æ¢å¤æˆ‘çš„æ•°æ®" "ä¸æ¢å¤" "请在下方输入您的当å‰å¤‡ä»½å¯†ç ï¼š" diff --git a/packages/BackupRestoreConfirmation/res/values-zh-rTW/strings.xml b/packages/BackupRestoreConfirmation/res/values-zh-rTW/strings.xml index 5afb226cde3f067176a278b6f43748129097d812..4da6114e18b5666456e7b496b7af4bac98704576 100644 --- a/packages/BackupRestoreConfirmation/res/values-zh-rTW/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-zh-rTW/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "完整備份" "完整還原" - "系統收到將所有資料完整備份至連線電腦的è¦æ±‚,請å•æ‚¨å…許進行備份嗎?"\n\n"如果您本人並未æ出備份è¦æ±‚,請勿å…許繼續進行這項作業。" + "系統收到將所有資料完整備份至連線電腦的è¦æ±‚,請å•æ‚¨å…許進行備份嗎?\n\n如果您本人並未æ出備份è¦æ±‚,請勿å…許繼續進行這項作業。" "備份我的資料" "ä¸è¦å‚™ä»½" - "系統收到從連線電腦完整還原所有資料的è¦æ±‚,請å•æ‚¨å…許進行還原嗎?"\n\n"如果您本人並未æ出還原è¦æ±‚,請勿å…許繼續進行這項作業。這項作業將å–代è£ç½®ä¸Šç¾æœ‰çš„全部資料ï¼" + "系統收到從連線電腦完整還原所有資料的è¦æ±‚,請å•æ‚¨å…許進行還原嗎?\n\n如果您本人並未æ出還原è¦æ±‚,請勿å…許繼續進行這項作業。這項作業將å–代è£ç½®ä¸Šç¾æœ‰çš„全部資料ï¼" "還原我的資料" "ä¸è¦é‚„原" "請在下é¢è¼¸å…¥æ‚¨ç›®å‰çš„備份密碼:" diff --git a/packages/BackupRestoreConfirmation/res/values-zu/strings.xml b/packages/BackupRestoreConfirmation/res/values-zu/strings.xml index 241bd371bde5d2c9bcee13fce4ac15350cf688d8..b62b7afaee97824e272bd6af8261204dbe291be4 100644 --- a/packages/BackupRestoreConfirmation/res/values-zu/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-zu/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Ukulondolozwa okuphelele" "Ukubuyisela okuphelele" - "Kucelwe ukwesekelwa ngokulondoloza okuphelele kwayo yonke imininingo ekwi-desktop yekhompuyutha exhunyiwe. Angifuni ukuvumel alokhu ukuthi kwenzeke?"\n\n"Uma kuwukuthi awuzange ucele ukuthi kwesekelwe ngokulondoloza wena uqobo lwakho, ungavumeli ukuthi lolu hlelo luqhubekele phambili." + "Kucelwe ukwesekelwa ngokulondoloza okuphelele kwayo yonke imininingo ekwi-desktop yekhompuyutha exhunyiwe. Angifuni ukuvumel alokhu ukuthi kwenzeke?\n\nUma kuwukuthi awuzange ucele ukuthi kwesekelwe ngokulondoloza wena uqobo lwakho, ungavumeli ukuthi lolu hlelo luqhubekele phambili." "Sekela ngokulondoloza imininingo yami" "Ungenzi isipele" - "Kucelwe ukubuyiselwa esimweni okuphelele kwayo yonke imininingo yakho kwi-desktop yekhompuyutha exhunyiswe. Ngabe ufuna ukuvumela lokhu ukuthi kwenzeke?"\n\n"Uma ungazange ucele ukuthi lokhu kwenzeke wena uqobo, ungavumeli lokhu ukuthi kuqhubekele phambili. Lokhu kuzothatha indawo yayo yonke imininingo ekhona njengamanje kwi-device!" + "Kucelwe ukubuyiselwa esimweni okuphelele kwayo yonke imininingo yakho kwi-desktop yekhompuyutha exhunyiswe. Ngabe ufuna ukuvumela lokhu ukuthi kwenzeke?\n\nUma ungazange ucele ukuthi lokhu kwenzeke wena uqobo, ungavumeli lokhu ukuthi kuqhubekele phambili. Lokhu kuzothatha indawo yayo yonke imininingo ekhona njengamanje kwi-device!" "Buyisela esimweni imininingo yami" "Ungabuyiseli esimweni" "Sicela ufake iphasiwedi yakho yamanje yokwenza isipele ngezansi:" diff --git a/packages/DefaultContainerService/Android.mk b/packages/DefaultContainerService/Android.mk index 56b800504db3708beace13e059621887170e164c..99611683c6f4bd5c4cc3dee9a2c143bd9b650228 100644 --- a/packages/DefaultContainerService/Android.mk +++ b/packages/DefaultContainerService/Android.mk @@ -11,6 +11,8 @@ LOCAL_REQUIRED_MODULES := libdefcontainer_jni LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true + include $(BUILD_PACKAGE) include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index d3bd197a4d0e3573d0d801a9a7316f67b9233340..6e34bbb08a394893b5f167865bcf9895c8ddc765 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -146,6 +146,8 @@ public class DefaultContainerService extends IntentService { Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " Security: " + e.getMessage()); return PackageManager.INSTALL_FAILED_INVALID_APK; + } finally { + IoUtils.closeQuietly(autoOut); } } @@ -293,10 +295,10 @@ public class DefaultContainerService extends IntentService { try { while ((item = pm.nextPackageToClean(item)) != null) { final UserEnvironment userEnv = new UserEnvironment(item.userId); - eraseFiles(userEnv.getExternalStorageAppDataDirectory(item.packageName)); - eraseFiles(userEnv.getExternalStorageAppMediaDirectory(item.packageName)); + eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName)); + eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName)); if (item.andCode) { - eraseFiles(userEnv.getExternalStorageAppObbDirectory(item.packageName)); + eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName)); } } } catch (RemoteException e) { @@ -304,6 +306,12 @@ public class DefaultContainerService extends IntentService { } } + void eraseFiles(File[] paths) { + for (File path : paths) { + eraseFiles(path); + } + } + void eraseFiles(File path) { if (path.isDirectory()) { String[] files = path.list(); diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..2f97809c1818bf5d055453d60c7b2fa47813166b --- /dev/null +++ b/packages/DocumentsUI/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 guava + +LOCAL_PACKAGE_NAME := DocumentsUI +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..6faf7f870f5923bf686ae03525ec94bc7d323775 --- /dev/null +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/DocumentsUI/res/animator-ldrtl/dir_down.xml b/packages/DocumentsUI/res/animator-ldrtl/dir_down.xml new file mode 100644 index 0000000000000000000000000000000000000000..6c7e2244c7129d24be21d3027e91b5de467d7d25 --- /dev/null +++ b/packages/DocumentsUI/res/animator-ldrtl/dir_down.xml @@ -0,0 +1,22 @@ + + + diff --git a/packages/DocumentsUI/res/animator-ldrtl/dir_up.xml b/packages/DocumentsUI/res/animator-ldrtl/dir_up.xml new file mode 100644 index 0000000000000000000000000000000000000000..8e2925c9760dd587500c460bff3dc5fc0962418b --- /dev/null +++ b/packages/DocumentsUI/res/animator-ldrtl/dir_up.xml @@ -0,0 +1,22 @@ + + + diff --git a/packages/DocumentsUI/res/animator/dir_down.xml b/packages/DocumentsUI/res/animator/dir_down.xml new file mode 100644 index 0000000000000000000000000000000000000000..7f547f135cd62ab7d6e6834c7a8fe1c3e54d241c --- /dev/null +++ b/packages/DocumentsUI/res/animator/dir_down.xml @@ -0,0 +1,22 @@ + + + diff --git a/packages/DocumentsUI/res/animator/dir_frozen.xml b/packages/DocumentsUI/res/animator/dir_frozen.xml new file mode 100644 index 0000000000000000000000000000000000000000..b541d137c5d9f3671f28c42c26a5494c3629a10e --- /dev/null +++ b/packages/DocumentsUI/res/animator/dir_frozen.xml @@ -0,0 +1,21 @@ + + + diff --git a/packages/DocumentsUI/res/animator/dir_up.xml b/packages/DocumentsUI/res/animator/dir_up.xml new file mode 100644 index 0000000000000000000000000000000000000000..fda0faf4eff312d9e9185cee8e0b4ec94ef43fd8 --- /dev/null +++ b/packages/DocumentsUI/res/animator/dir_up.xml @@ -0,0 +1,22 @@ + + + diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_breadcrumb_arrow_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_breadcrumb_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..7c4c1a699d3b7611f3d51c7102a4f1861f228391 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_breadcrumb_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_accept.png b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_accept.png new file mode 100644 index 0000000000000000000000000000000000000000..649985d4f98e485656e92f5e084a7fd15c0c26bc Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_accept.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_cancel.png b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..791bf6da75819213368761370dac72dbba723e16 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_cancel.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_select_item.png b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_select_item.png new file mode 100644 index 0000000000000000000000000000000000000000..6c32af17afd047f943ea7c35f2de33095b960bb0 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_select_item.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_alert.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc4e05e2ac5e1a7f51fd87d17da46b1f5597cb8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_alert.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_info.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_info.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb076c77266a9959a438bcd553aee23168e5409 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_info.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow_am.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..904d5253672caaa4ebf688b7227f1ffc4886c4c9 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_album.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_album.png new file mode 100644 index 0000000000000000000000000000000000000000..179db33f3dcb841fd4c9cf9cd1f9e7299fc00d89 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_album.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_apk.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_apk.png new file mode 100644 index 0000000000000000000000000000000000000000..8704a7859ef725a5d37e7d7f40661c8e130116f2 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_apk.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_audio_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_audio_am.png new file mode 100644 index 0000000000000000000000000000000000000000..465838d1e27ff4f58cf71c0b5bc691681aa66501 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_audio_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_certificate.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_certificate.png new file mode 100644 index 0000000000000000000000000000000000000000..434a6e6333cac8e6248aa73f2f8e7a17e3dc37b1 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_certificate.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_codes.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_codes.png new file mode 100644 index 0000000000000000000000000000000000000000..940d185db023fe65cd425121c265eac91daa062a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_codes.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_compressed.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_compressed.png new file mode 100644 index 0000000000000000000000000000000000000000..35cdc1fba4a029e014926032ab36224c0abcd0c4 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_compressed.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_contact_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_contact_am.png new file mode 100644 index 0000000000000000000000000000000000000000..8f3b82c16768b5cd3328090a47116e5bc2290c84 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_contact_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_event_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_event_am.png new file mode 100644 index 0000000000000000000000000000000000000000..a3df8936db0a72e7820eaa70536ee94fb0035ded Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_event_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_font.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_font.png new file mode 100644 index 0000000000000000000000000000000000000000..92225bad092920c808a8b19d5a161e310837405a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_font.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_generic_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_generic_am.png new file mode 100644 index 0000000000000000000000000000000000000000..55b9b7d3c2560f2cc35c7b0ef24febd9d821671c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_generic_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_image.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_image.png new file mode 100644 index 0000000000000000000000000000000000000000..72b611db3e64b7d568ff33f1dd8e5146397bc66f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_image.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_pdf.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..e08b0e683b766355d8c79b9215492fb57baab262 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_pdf.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_presentation.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_presentation.png new file mode 100644 index 0000000000000000000000000000000000000000..0c55e8c614dd3ab731cae5b4aad2f002da4ff9ea Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_presentation.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_spreadsheet_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_spreadsheet_am.png new file mode 100644 index 0000000000000000000000000000000000000000..880564ec506c68bb3be6b7830e50393a5fdcc89e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_spreadsheet_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_text_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_text_am.png new file mode 100644 index 0000000000000000000000000000000000000000..cb6016588f77ef49859a407a8abcf49f66ecf98a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_text_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_video_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_video_am.png new file mode 100644 index 0000000000000000000000000000000000000000..9a942d28e66a971d1100825c52b50f3e99e5ee1f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_video_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_glyph.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_glyph.png new file mode 100644 index 0000000000000000000000000000000000000000..251ecfb0ab5f7faa8465250119403d9e2e07e98c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_glyph.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0d75172a1eb11ac974d2273484caf60c66874be9 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_shadow_am.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..4a710cede463ab447c77a056000e4beacd6d16b4 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_shadow_tablet_am.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_shadow_tablet_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a1bbc8b7965dbb8af17b879c5aa6e044373506e6 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_shadow_tablet_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..403eddb4f90a6e0ab8a4603cc60c0a2926077eb5 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_background.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..7c3d69d472659d95f691ca55637ce8728f7b46d0 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_background.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_focused.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_focused.9.png new file mode 100644 index 0000000000000000000000000000000000000000..8b900945cc1fdce1fa1be2467f4d0d15ba4623ba Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_focused.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_pressed.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_pressed.9.png new file mode 100644 index 0000000000000000000000000000000000000000..1e41d7aa609fa94048911309ae6944601aa1bb0d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_card_pressed.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_folder.png b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..a6e56ea86bac53bd5d28a448adad9a1140c435f6 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_folder.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_gradient_bg.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_gradient_bg.9.png new file mode 100644 index 0000000000000000000000000000000000000000..b896c556626262bdb97bd0ce8a701c0bc5977876 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_gradient_bg.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_copy.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..c907bf61c047c095935c7c6bfe0ce02afcd69d29 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_copy.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_delete.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..1fe7af74f9807abd932614ac351cd93c3a35226f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_delete.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_disconnect_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_disconnect_am.png new file mode 100644 index 0000000000000000000000000000000000000000..8a88407fe1a64e85dd6c18bbfc0e4a458571530f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_disconnect_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_new_folder_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_new_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..638c8124e3d22039052bbe26c902ff2cad47b3c8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_new_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_overflow.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..2a007d2ee9baad5f97d38f3bc8bd6c6d4914a6dd Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_overflow.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_rename_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_rename_am.png new file mode 100644 index 0000000000000000000000000000000000000000..27563277ea63c8f2527939bf847251c0fff9fec6 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_rename_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png new file mode 100644 index 0000000000000000000000000000000000000000..b00328b50e1c24e7c487f23943e0360e527d5c41 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_settings.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..03e0cc7aa32126fac29e3d62733395ab6e2bbb79 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_settings.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_share.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_share.png new file mode 100644 index 0000000000000000000000000000000000000000..cf7d2f4327e9a454702346dfb32fda355884d290 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_share.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sortby_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sortby_am.png new file mode 100644 index 0000000000000000000000000000000000000000..0d4cdc197f6d097a44ec1225bf6e281468d5e11d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sortby_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_undo_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_undo_am.png new file mode 100644 index 0000000000000000000000000000000000000000..20dce0fa6793e070bbe03bb48ab50d02a421a46f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_undo_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_grid.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_grid.png new file mode 100644 index 0000000000000000000000000000000000000000..3f3b536d2ab3cb2daca2181e6c425958f8d09ab8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_grid.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_list.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_list.png new file mode 100644 index 0000000000000000000000000000000000000000..79bffc9a1882be7552c1e83b7346f2b4081f8d00 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_list.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_open_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_open_am.png new file mode 100644 index 0000000000000000000000000000000000000000..595c4b9039d476c6c08d34d8253271b72fc596b8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_open_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_popout_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_popout_am.png new file mode 100644 index 0000000000000000000000000000000000000000..37005129bd85c5c9b3a09bbdbaa1c2b9a0ba827d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_popout_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_download.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_download.png new file mode 100644 index 0000000000000000000000000000000000000000..52f1c7044ef75bcbf8894f97b367fc1563ce92c1 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_root_download.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_folder_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..915e11826ea7b82c27d2c7b02574067c3f656ae8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_root_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_recent.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_recent.png new file mode 100644 index 0000000000000000000000000000000000000000..303b7f99ffae90aa31abec2e4ebc2d4b714f75ba Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_root_recent.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_sdcard.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_sdcard.png new file mode 100644 index 0000000000000000000000000000000000000000..2375e17575ee44841385fb55ef355733c18c86e7 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_root_sdcard.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_usb.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_usb.png new file mode 100644 index 0000000000000000000000000000000000000000..5c0c87b240137f9719e6423b12fd44ecf12eac38 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_root_usb.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_subdirectory_arrow_am.png b/packages/DocumentsUI/res/drawable-hdpi/ic_subdirectory_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..99060cd6fe2c59673745ba8f6959d63b2adc813a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_subdirectory_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_breadcrumb_arrow_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_breadcrumb_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..09e77afb6b588cd417f47197fc3d0291b656b332 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_breadcrumb_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_accept.png b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_accept.png new file mode 100644 index 0000000000000000000000000000000000000000..f42be13898408f64f1ba6164142619f336fa9268 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_accept.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_cancel.png b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..b47e30623091ccc50e0b8f587ffbffdff74e912d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_cancel.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_select_item.png b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_select_item.png new file mode 100644 index 0000000000000000000000000000000000000000..903a04132d497eddde7799b1b5d9ee06268eb865 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_select_item.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_alert.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..4835d5fa37f7a57d202a3c914e0bd84a5bf8691b Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_alert.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_info.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_info.png new file mode 100644 index 0000000000000000000000000000000000000000..2d2944273c7c79c51e9ae9b9b68f15f01701d8ad Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_info.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow_am.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..068619be0b43c378022dc61988c4cbe47f0fa8ee Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_album.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_album.png new file mode 100644 index 0000000000000000000000000000000000000000..318dd5bd3a09fd65b8375cc8749203497fbe8a73 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_album.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_apk.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_apk.png new file mode 100644 index 0000000000000000000000000000000000000000..932995ee7037ed6c8364b1afdd8ee1fb51d90a9b Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_apk.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_audio_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_audio_am.png new file mode 100644 index 0000000000000000000000000000000000000000..cb94d991fe7973d835c38014864c47b99bc7a268 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_audio_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_certificate.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_certificate.png new file mode 100644 index 0000000000000000000000000000000000000000..240d7f43fc44da3d1ec7ad7a96403aa443cb0f3d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_certificate.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_codes.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_codes.png new file mode 100644 index 0000000000000000000000000000000000000000..6c6aad61e4bc98c68402309a451d7af0f32d830c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_codes.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_compressed.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_compressed.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc7bea1d2e97e884be59ca8862729a439ae0255 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_compressed.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_contact_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_contact_am.png new file mode 100644 index 0000000000000000000000000000000000000000..290ad3a915e613838009fc8e742692e27f5f4969 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_contact_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_event_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_event_am.png new file mode 100644 index 0000000000000000000000000000000000000000..e5eda72cd5280c43053e04c2e8e3e4396fb5f56a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_event_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_font.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_font.png new file mode 100644 index 0000000000000000000000000000000000000000..00bd478ad3e8a4ce1a8cb271e716ed85dc4b40be Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_font.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_generic_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_generic_am.png new file mode 100644 index 0000000000000000000000000000000000000000..a1bd14eafbb0b2f7b19b6a0b97856ef07d98a33e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_generic_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_image.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_image.png new file mode 100644 index 0000000000000000000000000000000000000000..b81b1e5a126f9f7de763a5d8bd3fe4779a215d3e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_image.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_pdf.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..3381c42ffbf3f6633c6467c7ef8c7186d102e5ff Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_pdf.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_presentation.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_presentation.png new file mode 100644 index 0000000000000000000000000000000000000000..68cc9711b0f683045b1ea737df692a812274e6fd Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_presentation.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_spreadsheet_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_spreadsheet_am.png new file mode 100644 index 0000000000000000000000000000000000000000..2934e5ace3da114df513343a9b3996c88553025f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_spreadsheet_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_text_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_text_am.png new file mode 100644 index 0000000000000000000000000000000000000000..95565b3445d7fde68ce57d57ddc68fb854f295d4 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_text_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_video_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_video_am.png new file mode 100644 index 0000000000000000000000000000000000000000..3a5b798e11cb73c573a72a730d37ec0ec5810bb2 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_video_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_glyph.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_glyph.png new file mode 100644 index 0000000000000000000000000000000000000000..ae0da3462a24bf4c1d3f2f7bb371ca83f6d98c46 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_glyph.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0d75172a1eb11ac974d2273484caf60c66874be9 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_shadow_am.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..9343a39748739a47b13ab50cd56ef622eff7f874 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_shadow_tablet_am.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_shadow_tablet_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..fabb56ed2980d179a88f40529e364b40c4194cfd Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_shadow_tablet_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..9a9cf5ede773a598acc8f90defc68d114a6bc2c3 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_background.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..567a06bc760663cbd166ee857692694e5a55ec09 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_background.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_focused.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_focused.9.png new file mode 100644 index 0000000000000000000000000000000000000000..1525572f5b88afe9a8645a90b3917b13c08b499c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_focused.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_pressed.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_pressed.9.png new file mode 100644 index 0000000000000000000000000000000000000000..16c929640006da20763d45e4844bfed41f8a58b3 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_card_pressed.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_folder.png b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..6e63b8c6ad44be1800c0196a0ace632d6e75a9dd Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_folder.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_gradient_bg.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_gradient_bg.9.png new file mode 100644 index 0000000000000000000000000000000000000000..11208640b3abb804fb98a3521571c2ab753c555e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_gradient_bg.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_copy.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..fbf5c888bb5009ace52246562d4480fffc43a0fd Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_copy.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_delete.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb4bf20915f0ea5edf017a4e95de34397e80271 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_delete.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_disconnect_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_disconnect_am.png new file mode 100644 index 0000000000000000000000000000000000000000..96b01b975572e6a8398f1755211c5f01cb6dcb10 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_disconnect_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_new_folder_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_new_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9580947b58c82433ce40b564f6ceefb3a484e1 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_new_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_overflow.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..7a6382824c77f934e9c2182372cc349b0043b212 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_overflow.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_rename_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_rename_am.png new file mode 100644 index 0000000000000000000000000000000000000000..9ab2f78b7bba5c862e56b4912e67e681526e790d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_rename_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_search.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_search.png new file mode 100644 index 0000000000000000000000000000000000000000..2d0ab8a6c61e5c4826f43c09debd411e46e8b362 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_search.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_settings.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..cf5575a4e834ab0be57f721c8707b0680dc3ecac Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_settings.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_share.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_share.png new file mode 100644 index 0000000000000000000000000000000000000000..368fbd610baf3e5ec6e8ec780f74f1d7c909aa60 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_share.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_sortby_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_sortby_am.png new file mode 100644 index 0000000000000000000000000000000000000000..2768b1c8de74ca8c35235152e8a3b9582df20b06 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_sortby_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_undo_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_undo_am.png new file mode 100644 index 0000000000000000000000000000000000000000..d56db426f0569af63ba866816875461885f1c80b Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_undo_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_grid.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_grid.png new file mode 100644 index 0000000000000000000000000000000000000000..0a0c8f1230b1ab8a4edc96df029bfc3c8c9903b7 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_grid.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_list.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_list.png new file mode 100644 index 0000000000000000000000000000000000000000..8a724ac187354df09b5b63cab8b0c2c5326d9b8d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_list.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_open_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_open_am.png new file mode 100644 index 0000000000000000000000000000000000000000..adfacc1e50f7611390e256030add29a338f0b9c0 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_open_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_popout_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_popout_am.png new file mode 100644 index 0000000000000000000000000000000000000000..b17de2d6a059629ecc9a702c3dfb6ac8847644b8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_popout_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_download.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_download.png new file mode 100644 index 0000000000000000000000000000000000000000..4f903df95a05187b3a9077cc3840195b93ef42e6 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_root_download.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_folder_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..4352d08d9b1ba70d71423e5ca412a2dfa622efe8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_root_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_recent.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_recent.png new file mode 100644 index 0000000000000000000000000000000000000000..bf9b1b6e113140955f4304e9a7045150484b6b1c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_root_recent.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_sdcard.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_sdcard.png new file mode 100644 index 0000000000000000000000000000000000000000..6adc2a373a6c96890aacc3ddcd25f06dd21813a1 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_root_sdcard.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_usb.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_usb.png new file mode 100644 index 0000000000000000000000000000000000000000..d318dba9b1976be0266909c17cd72f2b5dfaa766 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_root_usb.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_subdirectory_arrow_am.png b/packages/DocumentsUI/res/drawable-mdpi/ic_subdirectory_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..a7a2b129c1d2f0e85075ba2a7675d76fa8d39af2 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_subdirectory_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_breadcrumb_arrow_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_breadcrumb_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..33c8f278ea7a8106eaceffd8ffe24bf9eb4bea00 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_breadcrumb_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_accept.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_accept.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9641d65128e2016f0c538ed1788cdd9a09b5a9 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_accept.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_cancel.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..9c3d008d9f1c6e90c238eb8420032c03516127e2 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_cancel.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_select_item.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_select_item.png new file mode 100644 index 0000000000000000000000000000000000000000..4cf4f3f30737c2ec455b77d04f16c44045801fc2 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_select_item.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_alert.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..17f9f9ef4c79e19838dd56716463f51a5e2d9df5 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_alert.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_info.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_info.png new file mode 100644 index 0000000000000000000000000000000000000000..2f9cc588af228be0a402f2f0c9f150aa35fe55e8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_info.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow_am.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e38a8685dcea8ab8d3100c44e3b805bc2b570443 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_album.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_album.png new file mode 100644 index 0000000000000000000000000000000000000000..e67aa8d132db9dcd0a149213d610312fb4902691 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_album.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_apk.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_apk.png new file mode 100644 index 0000000000000000000000000000000000000000..d0e2594f29a944e94c420c45b824621fdbe99c78 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_apk.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_audio_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_audio_am.png new file mode 100644 index 0000000000000000000000000000000000000000..2e66f03727417e467d4b291b0570dbe265964a74 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_audio_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_certificate.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_certificate.png new file mode 100644 index 0000000000000000000000000000000000000000..64e0d4254ca74d56343176bd39fc2fd2306ca544 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_certificate.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_codes.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_codes.png new file mode 100644 index 0000000000000000000000000000000000000000..a4f70ba2c53fd39ce710d2dc2b8cf4cdd776ee6a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_codes.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_compressed.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_compressed.png new file mode 100644 index 0000000000000000000000000000000000000000..4897221c597d8d3f1b19a280e12f616e6993a170 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_compressed.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_contact_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_contact_am.png new file mode 100644 index 0000000000000000000000000000000000000000..4cec994e6bbecaf85978846dd8b16714eab26710 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_contact_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_event_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_event_am.png new file mode 100644 index 0000000000000000000000000000000000000000..5e46b71eeb800a6c1b41ddda2ed378c27d2298ad Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_event_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_font.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_font.png new file mode 100644 index 0000000000000000000000000000000000000000..977cfd2a84f5226e7f0376b35ca3b44ab8a7f43e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_font.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_generic_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_generic_am.png new file mode 100644 index 0000000000000000000000000000000000000000..e05c4b48d52d225c8418067d02647320cf779b06 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_generic_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_image.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_image.png new file mode 100644 index 0000000000000000000000000000000000000000..98d3f7954123c0441efb6be151aad5eb0205df3b Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_image.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_pdf.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..ff2ff14311de8a5d364bb2782b32c9d02c41bc92 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_pdf.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_presentation.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_presentation.png new file mode 100644 index 0000000000000000000000000000000000000000..291737751acc0a693f2e24febcbbeab4c55fcc5f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_presentation.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_spreadsheet_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_spreadsheet_am.png new file mode 100644 index 0000000000000000000000000000000000000000..87c6538c1bc543030a667368ab513a0ab791f28f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_spreadsheet_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_text_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_text_am.png new file mode 100644 index 0000000000000000000000000000000000000000..97c45002cbbc434cb7468b240a809e1f7a25c912 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_text_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_video_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_video_am.png new file mode 100644 index 0000000000000000000000000000000000000000..1a8e632c288803ff7fc81692df77bfb7258f28da Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_video_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_glyph.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_glyph.png new file mode 100644 index 0000000000000000000000000000000000000000..7402c6d3bada22ddb289317059bdf62fa7f1476e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_glyph.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0d75172a1eb11ac974d2273484caf60c66874be9 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_shadow_am.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..027c64aa292539073a73ece7005d19cead70fd68 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_shadow_tablet_am.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_shadow_tablet_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..2c39a67ffc228310ac3179ecd2260980b7c05320 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_shadow_tablet_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..205c34bcbe6084128ef0f3e776a2b99b1b4d55b3 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_background.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..8f7f4ab9423693b79e3fc8d7029fdfd166e794b7 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_background.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_focused.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_focused.9.png new file mode 100644 index 0000000000000000000000000000000000000000..b82ae20239a5a917c304b30b452de7318fa019d9 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_focused.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_pressed.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_pressed.9.png new file mode 100644 index 0000000000000000000000000000000000000000..edd626675c2f06f23dfd8ff98ebdb0f1e53f9e5c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_card_pressed.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_folder.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..c3af9ec372accaf7ba8a19f6ac9011caee15d142 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_folder.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_gradient_bg.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_gradient_bg.9.png new file mode 100644 index 0000000000000000000000000000000000000000..60ce8d548fbeabf8c908d0cb42ba7755e2b00832 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_gradient_bg.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_copy.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..c650185a7203f90cf30f872e1639ca500acef3d8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_copy.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_delete.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..0771ed2c1f208938b36f6efebd43c4a986404114 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_delete.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_disconnect_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_disconnect_am.png new file mode 100644 index 0000000000000000000000000000000000000000..91c31e3baecb11a8095470c0aae2978ab0837026 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_disconnect_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_new_folder_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_new_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..f06b298fdf82d515a55cfd2647153c1b3c8744fa Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_new_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_overflow.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a7eaab075ef11048e711b9e9d093a1f877bb22 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_overflow.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_rename_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_rename_am.png new file mode 100644 index 0000000000000000000000000000000000000000..17e09b363a5f4128f4c9d2cba78186a79e4870d6 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_rename_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_search.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_search.png new file mode 100644 index 0000000000000000000000000000000000000000..0ab604f5479c4fac3159ea71aa3c71a7b06fd2ff Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_search.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_settings.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..5054fc8fefdf0b17d55374da7e603d5bcb1b2e1c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_settings.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_share.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_share.png new file mode 100644 index 0000000000000000000000000000000000000000..d3d386e28835bc1a79d5cb7d8c4ea4f27c26fc4a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_share.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_sortby_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_sortby_am.png new file mode 100644 index 0000000000000000000000000000000000000000..f24ca1a855fad2b5064d325ab6934f13bae47ede Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_sortby_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_undo_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_undo_am.png new file mode 100644 index 0000000000000000000000000000000000000000..82c1a30f1f1caec28c1ca713f5c305d2a9bb4066 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_undo_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_grid.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_grid.png new file mode 100644 index 0000000000000000000000000000000000000000..02583123cff78172c155f6db0e44ae0d295ebabf Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_grid.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_list.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_list.png new file mode 100644 index 0000000000000000000000000000000000000000..ccace9d278f1474f8f31010da7052fca4abe8b0d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_list.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_open_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_open_am.png new file mode 100644 index 0000000000000000000000000000000000000000..a56940a1d5b73e3f483b5407562689db4f0908f5 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_open_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_popout_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_popout_am.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a0af405a89938e691e8c93d4a2acece7667a5b Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_popout_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_download.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_download.png new file mode 100644 index 0000000000000000000000000000000000000000..6c6447e59f44a06d974fc02e7e86ef8d1d3635c3 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_download.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_folder_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..c916e0be7687f729aeacbf0a69bf4d0f2484a687 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_recent.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_recent.png new file mode 100644 index 0000000000000000000000000000000000000000..714f2ee231555cfc2a59312414e464fbd9f78bb5 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_recent.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_sdcard.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_sdcard.png new file mode 100644 index 0000000000000000000000000000000000000000..6016c080c499600db3dc52f4e1efa5ca04dfeebf Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_sdcard.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_usb.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_usb.png new file mode 100644 index 0000000000000000000000000000000000000000..b05b9a4d045088c010565e17f9ed9be9b4fc64ea Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_usb.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_subdirectory_arrow_am.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_subdirectory_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..1da8196588823d201dc0b051f8b6f0b29e338b0f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_subdirectory_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_breadcrumb_arrow_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_breadcrumb_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..06681e3c1c3510f603a716a89f97e45207dd3d0a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_breadcrumb_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_accept.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_accept.png new file mode 100644 index 0000000000000000000000000000000000000000..ac88818c154bd1988aeeecf38fc0714717dd018b Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_accept.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_cancel.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..88356c7c03b6068dae15a4f179dac55c29b27c54 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_cancel.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_select_item.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_select_item.png new file mode 100644 index 0000000000000000000000000000000000000000..75658db58dc0359e82d25d4008d694bc9d0edc6a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_select_item.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_alert.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..8bee0dc21f363fa7cbdbbf3e8aef2164d61bf8ef Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_alert.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_info.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_info.png new file mode 100644 index 0000000000000000000000000000000000000000..ad6c59b33b7c1420a0f462cdb761c748ab08cdf7 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_info.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow_am.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0b332e410a77a020cd9cc239f86b0a2c468a908d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_album.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_album.png new file mode 100644 index 0000000000000000000000000000000000000000..4c56bd0c9445858d7f2cd8c02a34e940a668c5fe Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_album.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_apk.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_apk.png new file mode 100644 index 0000000000000000000000000000000000000000..5f642293fbd5855ab597357681e20832a2f32dd5 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_apk.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_audio_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_audio_am.png new file mode 100644 index 0000000000000000000000000000000000000000..48ab9c77f50ae5fba07d2fdb6fc7c325abddca9d Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_audio_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_certificate.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_certificate.png new file mode 100644 index 0000000000000000000000000000000000000000..68e619e6c45da0b9c6fb2ddc13d41256f79ea8dc Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_certificate.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_codes.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_codes.png new file mode 100644 index 0000000000000000000000000000000000000000..945119aa105d58b90ca006641d90deb8f075cbbe Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_codes.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_compressed.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_compressed.png new file mode 100644 index 0000000000000000000000000000000000000000..bf49d787fcc2ade27365357ae9e54e2fd17c1fc2 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_compressed.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_contact_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_contact_am.png new file mode 100644 index 0000000000000000000000000000000000000000..5263365008a6eed255983965a870496c9c6f1bec Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_contact_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_event_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_event_am.png new file mode 100644 index 0000000000000000000000000000000000000000..77a0faec5c50a996d788cd3f97163ddb554b6d04 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_event_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_font.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_font.png new file mode 100644 index 0000000000000000000000000000000000000000..30d2c4c383c5a9d7393dd3e2aefde02aadc464ca Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_font.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_generic_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_generic_am.png new file mode 100644 index 0000000000000000000000000000000000000000..c098866320d2d6190598e2058ea405291aae01c2 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_generic_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_image.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_image.png new file mode 100644 index 0000000000000000000000000000000000000000..06d8d9c5614d1da5e28544de10fd25977e013cd5 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_image.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_pdf.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..a3b146bdd76b3e99d755e7ed552947fbe931e90f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_pdf.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_presentation.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_presentation.png new file mode 100644 index 0000000000000000000000000000000000000000..c09d6ab855c3f8ce264d634ba6cad14741ce18fa Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_presentation.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_spreadsheet_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_spreadsheet_am.png new file mode 100644 index 0000000000000000000000000000000000000000..2170e666d1fac44ea64898de8cdae2655c8bb846 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_spreadsheet_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_text_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_text_am.png new file mode 100644 index 0000000000000000000000000000000000000000..bc4ce7921cbab6005d551cb4a086063eeb9bcc97 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_text_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_video_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_video_am.png new file mode 100644 index 0000000000000000000000000000000000000000..42d8ec174bef1f2607ef9ffc13064c15ff1c21e3 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_video_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_glyph.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_glyph.png new file mode 100644 index 0000000000000000000000000000000000000000..416069959c481f8cf3b33d701484839589cca3c0 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_glyph.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..32b5f9880a892b6616f1ff17ad5e304fc192acad Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_shadow_am.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_shadow_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..1a59e1a8c09106564d2e883e9a9638ab024231ba Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_shadow_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_shadow_tablet_am.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_shadow_tablet_am.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9579097fdfa5a0193a3d6af35bbcbb8d642b65 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_shadow_tablet_am.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..f47d50a834b8f3c4823b75382342bfb76c6cc74e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_background.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..7bbaf9dc4f7c4e1044d95bddbd4164f7f59b4c3a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_background.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_focused.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_focused.9.png new file mode 100644 index 0000000000000000000000000000000000000000..901af807f7a7c6f2d5bdd32ad9cba549ba3d1861 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_focused.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_pressed.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_pressed.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e21e350e2e933c25780e6ca6047463bbf6e6e39f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_card_pressed.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_folder.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..86a74cdabc51d289b2aeeb58fcf015f96cb3429a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_folder.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_gradient_bg.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_gradient_bg.9.png new file mode 100644 index 0000000000000000000000000000000000000000..988c85630ded8de7ede2975b412eceb74b9b7a4c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_gradient_bg.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_copy.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..f23e23c5c863de395e76d6da599fa8cc3205904e Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_copy.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_delete.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..f67c72e840d99fb589ab9e6d7410ca98b8a584b5 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_delete.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_disconnect_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_disconnect_am.png new file mode 100644 index 0000000000000000000000000000000000000000..676d0f76972b9ea199c1bfa5ba4b4fc448fdfc8a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_disconnect_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_new_folder_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_new_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..b17ba1d0c2c915568c9a26cf0944c1c04053d638 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_new_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_overflow.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..58f13817ef8273f4aa0708bce60b0e4383b195a8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_overflow.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_rename_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_rename_am.png new file mode 100644 index 0000000000000000000000000000000000000000..eed0eafb9b0790a94f6882767e60bf16eb38b709 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_rename_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_search.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_search.png new file mode 100644 index 0000000000000000000000000000000000000000..40fb3924fb67bd5d7e9b6cf94ccf63485ccf1588 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_search.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_settings.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..b988ab503877d6709964d484d6d466ec83fb2cbc Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_settings.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_share.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_share.png new file mode 100644 index 0000000000000000000000000000000000000000..6ace932eead9fe525ad1b05abfa0e6f883fe8dc4 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_share.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_sortby_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_sortby_am.png new file mode 100644 index 0000000000000000000000000000000000000000..8f19afa9882546ca25b4b465ee155029a8034849 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_sortby_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_undo_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_undo_am.png new file mode 100644 index 0000000000000000000000000000000000000000..e4c9f8aa0b79183121611be4a8c37dfd75e1295b Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_undo_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_grid.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_grid.png new file mode 100644 index 0000000000000000000000000000000000000000..9e27d63757cf2c0c8675efa0f99e096a108775df Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_grid.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_list.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_list.png new file mode 100644 index 0000000000000000000000000000000000000000..e4c679aa86261d00c724cfac23d43cd18675d5b9 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_list.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_open_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_open_am.png new file mode 100644 index 0000000000000000000000000000000000000000..b467962459bfebe244bf5447abf443b35a9cad7f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_open_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_popout_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_popout_am.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5a86f793f182f0f955b46e503e2620cceab4d8 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_popout_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_download.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_download.png new file mode 100644 index 0000000000000000000000000000000000000000..3b8afc9f191b3666144f3156bff2d2f37e3b25e7 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_download.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_folder_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_folder_am.png new file mode 100644 index 0000000000000000000000000000000000000000..077c851cee28e72fb5740303bd85445959badf1c Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_folder_am.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_recent.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_recent.png new file mode 100644 index 0000000000000000000000000000000000000000..a3215f2d4f397373e5fb486fe6c0d2806878e24f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_recent.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_sdcard.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_sdcard.png new file mode 100644 index 0000000000000000000000000000000000000000..873a5534ca41c2ffed5af220d55068461ba0d3db Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_sdcard.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_usb.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_usb.png new file mode 100644 index 0000000000000000000000000000000000000000..d213e7c28bce7a184c19be36d2489d9c5f51b562 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_usb.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_subdirectory_arrow_am.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_subdirectory_arrow_am.png new file mode 100644 index 0000000000000000000000000000000000000000..db53a011f09d21ad60291fc675db55ec30d809d0 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_subdirectory_arrow_am.png differ diff --git a/packages/DocumentsUI/res/drawable/ic_breadcrumb_arrow.xml b/packages/DocumentsUI/res/drawable/ic_breadcrumb_arrow.xml new file mode 100644 index 0000000000000000000000000000000000000000..0de7ddc9ba30a554a4c59cd052b8cfde35ba8dd3 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_breadcrumb_arrow.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_dir_shadow.xml b/packages/DocumentsUI/res/drawable/ic_dir_shadow.xml new file mode 100644 index 0000000000000000000000000000000000000000..1153e693f4d3b3a3ef02d01102aed3160a5160fb --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_dir_shadow.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/DocumentsUI/res/drawable/ic_doc_audio.xml b/packages/DocumentsUI/res/drawable/ic_doc_audio.xml new file mode 100644 index 0000000000000000000000000000000000000000..c6ccea63c1b073a8c4d62a404f240ab2df489faa --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_doc_audio.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_doc_contact.xml b/packages/DocumentsUI/res/drawable/ic_doc_contact.xml new file mode 100644 index 0000000000000000000000000000000000000000..e6455513928cd286087d7d3f77ab19ff74cea4b8 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_doc_contact.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_doc_event.xml b/packages/DocumentsUI/res/drawable/ic_doc_event.xml new file mode 100644 index 0000000000000000000000000000000000000000..dcbd91216d0aa0e5247fb2972b2ec588637a8c69 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_doc_event.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_doc_generic.xml b/packages/DocumentsUI/res/drawable/ic_doc_generic.xml new file mode 100644 index 0000000000000000000000000000000000000000..d5f64ed84852e1ad7ed2fd855875c7d8b3fbfa3b --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_doc_generic.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_doc_spreadsheet.xml b/packages/DocumentsUI/res/drawable/ic_doc_spreadsheet.xml new file mode 100644 index 0000000000000000000000000000000000000000..8c6e056be8588742e2e9cc4f590ff0ff3b7239d8 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_doc_spreadsheet.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_doc_text.xml b/packages/DocumentsUI/res/drawable/ic_doc_text.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac63ecb7516e66d52392bab84a1db29f86d05b66 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_doc_text.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_doc_video.xml b/packages/DocumentsUI/res/drawable/ic_doc_video.xml new file mode 100644 index 0000000000000000000000000000000000000000..e1962622ca7edbbbf0062cda6d1d7de34cbf9d5b --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_doc_video.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_drawer_shadow.xml b/packages/DocumentsUI/res/drawable/ic_drawer_shadow.xml new file mode 100644 index 0000000000000000000000000000000000000000..8d457be8a3225d946e6b5e74c04e85c5ee22b52e --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_drawer_shadow.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/DocumentsUI/res/drawable/ic_drawer_shadow_tablet.xml b/packages/DocumentsUI/res/drawable/ic_drawer_shadow_tablet.xml new file mode 100644 index 0000000000000000000000000000000000000000..382ebff764aff0b66eaf6c34b25a2a47d3daa080 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_drawer_shadow_tablet.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/DocumentsUI/res/drawable/ic_menu_disconnect.xml b/packages/DocumentsUI/res/drawable/ic_menu_disconnect.xml new file mode 100644 index 0000000000000000000000000000000000000000..e5b4c8d334bfb949c84c61297322ae6c848ab189 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_menu_disconnect.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_menu_new_folder.xml b/packages/DocumentsUI/res/drawable/ic_menu_new_folder.xml new file mode 100644 index 0000000000000000000000000000000000000000..42e7f90fbe555a55e822805eb7341480a727b530 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_menu_new_folder.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_menu_rename.xml b/packages/DocumentsUI/res/drawable/ic_menu_rename.xml new file mode 100644 index 0000000000000000000000000000000000000000..e332313cdce05189a0ad2af620a5a35d818d5884 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_menu_rename.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/DocumentsUI/res/drawable/ic_menu_sortby.xml b/packages/DocumentsUI/res/drawable/ic_menu_sortby.xml new file mode 100644 index 0000000000000000000000000000000000000000..d908582f0871628c010e90141e45da22fc0ea9fb --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_menu_sortby.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_menu_undo.xml b/packages/DocumentsUI/res/drawable/ic_menu_undo.xml new file mode 100644 index 0000000000000000000000000000000000000000..f25d90d24777ad9bf3404d59c2f53c25bcaf6883 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_menu_undo.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/DocumentsUI/res/drawable/ic_open.xml b/packages/DocumentsUI/res/drawable/ic_open.xml new file mode 100644 index 0000000000000000000000000000000000000000..faeb03d4fe7774e0d7041cf084e0c229359b3e2c --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_open.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_popout.xml b/packages/DocumentsUI/res/drawable/ic_popout.xml new file mode 100644 index 0000000000000000000000000000000000000000..223598d166bee4ffbe887f1879aac8d07f2803df --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_popout.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_root_folder.xml b/packages/DocumentsUI/res/drawable/ic_root_folder.xml new file mode 100644 index 0000000000000000000000000000000000000000..a3c8f617231f07f5ebb3106b2649983421158f1a --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_root_folder.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/ic_subdirectory_arrow.xml b/packages/DocumentsUI/res/drawable/ic_subdirectory_arrow.xml new file mode 100644 index 0000000000000000000000000000000000000000..0f66e2a6c58baaf1dcbb65c419cb499f1c045320 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_subdirectory_arrow.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/packages/DocumentsUI/res/drawable/item_background.xml b/packages/DocumentsUI/res/drawable/item_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec9be6d184476640aad69f90f2cbac2f64c00a32 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/item_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/DocumentsUI/res/drawable/item_doc_grid.xml b/packages/DocumentsUI/res/drawable/item_doc_grid.xml new file mode 100644 index 0000000000000000000000000000000000000000..3f036f7a21bdac0cfd9a37532d827e92fdf52c3c --- /dev/null +++ b/packages/DocumentsUI/res/drawable/item_doc_grid.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/packages/DocumentsUI/res/drawable/item_root.xml b/packages/DocumentsUI/res/drawable/item_root.xml new file mode 100644 index 0000000000000000000000000000000000000000..60d4ab0bb5858c2baa4f82d3d7df5a1534f27c1c --- /dev/null +++ b/packages/DocumentsUI/res/drawable/item_root.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml new file mode 100644 index 0000000000000000000000000000000000000000..adbb9da4323fbbe3fba742048e60e2c4928f1b95 --- /dev/null +++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/DocumentsUI/res/layout-sw720dp/activity.xml b/packages/DocumentsUI/res/layout-sw720dp/activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..9286277958b152873bbadb0ec7ba60b0755ff751 --- /dev/null +++ b/packages/DocumentsUI/res/layout-sw720dp/activity.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..2ef7e9c1f636c24cbfb7b8e70a85ee74b220e8ad --- /dev/null +++ b/packages/DocumentsUI/res/layout/activity.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + diff --git a/packages/DocumentsUI/res/layout/dialog_create_dir.xml b/packages/DocumentsUI/res/layout/dialog_create_dir.xml new file mode 100644 index 0000000000000000000000000000000000000000..54e26b40dfdfff4968529d421a5b6d7fa5258588 --- /dev/null +++ b/packages/DocumentsUI/res/layout/dialog_create_dir.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml new file mode 100644 index 0000000000000000000000000000000000000000..77cdc3ba067e04ac810c309414a5be65014ef89e --- /dev/null +++ b/packages/DocumentsUI/res/layout/fragment_directory.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/packages/DocumentsUI/res/layout/fragment_roots.xml b/packages/DocumentsUI/res/layout/fragment_roots.xml new file mode 100644 index 0000000000000000000000000000000000000000..c3a3da07e9330d67672d3545d3d21ff2b2bfa720 --- /dev/null +++ b/packages/DocumentsUI/res/layout/fragment_roots.xml @@ -0,0 +1,21 @@ + + + + diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml new file mode 100644 index 0000000000000000000000000000000000000000..891f0a07129fd850d2de39707ac5c810a33ca463 --- /dev/null +++ b/packages/DocumentsUI/res/layout/fragment_save.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml new file mode 100644 index 0000000000000000000000000000000000000000..222b5b63e14de2f253301b034e08ae4426a39c79 --- /dev/null +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml new file mode 100644 index 0000000000000000000000000000000000000000..8bdb6c9566f1e4184f5838916daad888a6a773b8 --- /dev/null +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml new file mode 100644 index 0000000000000000000000000000000000000000..2749aa668ddbd830ba5d857aee0778e9d55b766c --- /dev/null +++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..6fc77df16f28f9e52d6409846cfca265707cc53c --- /dev/null +++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml new file mode 100644 index 0000000000000000000000000000000000000000..c3c5021e999005e16f0705cf2e9da8b20e498e98 --- /dev/null +++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..ee62f9f5e27529fc288ee8a255b58d1623427a06 --- /dev/null +++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml @@ -0,0 +1,37 @@ + + + +

        + + + + + + + + diff --git a/packages/PrintSpooler/res/values-be/arrays.xml b/packages/PrintSpooler/res/values-be/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..d40278c3973da88cc9d5d4546ba46ce6f20155a7 --- /dev/null +++ b/packages/PrintSpooler/res/values-be/arrays.xml @@ -0,0 +1,33 @@ + + + + + + NA_LETTER + NA_GOVT_LETTER + NA_LEGAL + NA_JUNIOR_LEGAL + NA_LEDGER + NA_TABLOID + NA_INDEX_3X5 + NA_INDEX_4X6 + NA_INDEX_5X8 + NA_MONARCH + NA_QUARTO + NA_FOOLSCAP + + + diff --git a/packages/PrintSpooler/res/values-be/donottranslate.xml b/packages/PrintSpooler/res/values-be/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..7537aa50d20c25ea8e088f8b7b4575e4325aa8f8 --- /dev/null +++ b/packages/PrintSpooler/res/values-be/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + NA_LETTER + @string/mediasize_standard_north_america + + diff --git a/packages/PrintSpooler/res/values-ca/arrays.xml b/packages/PrintSpooler/res/values-ca/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..d40278c3973da88cc9d5d4546ba46ce6f20155a7 --- /dev/null +++ b/packages/PrintSpooler/res/values-ca/arrays.xml @@ -0,0 +1,33 @@ + + + + + + NA_LETTER + NA_GOVT_LETTER + NA_LEGAL + NA_JUNIOR_LEGAL + NA_LEDGER + NA_TABLOID + NA_INDEX_3X5 + NA_INDEX_4X6 + NA_INDEX_5X8 + NA_MONARCH + NA_QUARTO + NA_FOOLSCAP + + + diff --git a/packages/PrintSpooler/res/values-ca/donottranslate.xml b/packages/PrintSpooler/res/values-ca/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..7537aa50d20c25ea8e088f8b7b4575e4325aa8f8 --- /dev/null +++ b/packages/PrintSpooler/res/values-ca/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + NA_LETTER + @string/mediasize_standard_north_america + + diff --git a/packages/PrintSpooler/res/values-en-rCA/arrays.xml b/packages/PrintSpooler/res/values-en-rCA/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..d40278c3973da88cc9d5d4546ba46ce6f20155a7 --- /dev/null +++ b/packages/PrintSpooler/res/values-en-rCA/arrays.xml @@ -0,0 +1,33 @@ + + + + + + NA_LETTER + NA_GOVT_LETTER + NA_LEGAL + NA_JUNIOR_LEGAL + NA_LEDGER + NA_TABLOID + NA_INDEX_3X5 + NA_INDEX_4X6 + NA_INDEX_5X8 + NA_MONARCH + NA_QUARTO + NA_FOOLSCAP + + + diff --git a/packages/PrintSpooler/res/values-en-rCA/donottranslate.xml b/packages/PrintSpooler/res/values-en-rCA/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..fb1f5d6b97b457785547f15152980d8a26a9f055 --- /dev/null +++ b/packages/PrintSpooler/res/values-en-rCA/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + NA_LETTER + @string/mediasize_standard_north_america + + diff --git a/packages/PrintSpooler/res/values-en-rUS/arrays.xml b/packages/PrintSpooler/res/values-en-rUS/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..d40278c3973da88cc9d5d4546ba46ce6f20155a7 --- /dev/null +++ b/packages/PrintSpooler/res/values-en-rUS/arrays.xml @@ -0,0 +1,33 @@ + + + + + + NA_LETTER + NA_GOVT_LETTER + NA_LEGAL + NA_JUNIOR_LEGAL + NA_LEDGER + NA_TABLOID + NA_INDEX_3X5 + NA_INDEX_4X6 + NA_INDEX_5X8 + NA_MONARCH + NA_QUARTO + NA_FOOLSCAP + + + diff --git a/packages/PrintSpooler/res/values-en-rUS/donottranslate.xml b/packages/PrintSpooler/res/values-en-rUS/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..fb1f5d6b97b457785547f15152980d8a26a9f055 --- /dev/null +++ b/packages/PrintSpooler/res/values-en-rUS/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + NA_LETTER + @string/mediasize_standard_north_america + + diff --git a/packages/PrintSpooler/res/values-es-rUS/arrays.xml b/packages/PrintSpooler/res/values-es-rUS/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..d40278c3973da88cc9d5d4546ba46ce6f20155a7 --- /dev/null +++ b/packages/PrintSpooler/res/values-es-rUS/arrays.xml @@ -0,0 +1,33 @@ + + + + + + NA_LETTER + NA_GOVT_LETTER + NA_LEGAL + NA_JUNIOR_LEGAL + NA_LEDGER + NA_TABLOID + NA_INDEX_3X5 + NA_INDEX_4X6 + NA_INDEX_5X8 + NA_MONARCH + NA_QUARTO + NA_FOOLSCAP + + + diff --git a/packages/PrintSpooler/res/values-es-rUS/donottranslate.xml b/packages/PrintSpooler/res/values-es-rUS/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..fb1f5d6b97b457785547f15152980d8a26a9f055 --- /dev/null +++ b/packages/PrintSpooler/res/values-es-rUS/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + NA_LETTER + @string/mediasize_standard_north_america + + diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..3364979742362d6316ca3dbdb39b04077eb3ce6b --- /dev/null +++ b/packages/PrintSpooler/res/values-ja/arrays.xml @@ -0,0 +1,41 @@ + + + + + + JIS_B10 + JIS_B9 + JIS_B8 + JIS_B7 + JIS_b6 + JIS_b5 + JIS_b4 + JIS_b3 + JIS_b2 + JIS_b1 + JIS_b0 + JIS_EXEC + JPN_CHOU4 + JPN_CHOU3 + JPN_CHOU2 + JPN_HAGAKI + JPN_OUFUKU + JPN_KAHU + JPN_KAKU2 + JPN_YOU4 + + + diff --git a/packages/PrintSpooler/res/values-ja/donottranslate.xml b/packages/PrintSpooler/res/values-ja/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..d334ddd312cff0f175edca3dfb71697c1274e95f --- /dev/null +++ b/packages/PrintSpooler/res/values-ja/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + JIS_B5 + @string/mediasize_standard_japan + + diff --git a/packages/PrintSpooler/res/values-zh-rCN/arrays.xml b/packages/PrintSpooler/res/values-zh-rCN/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..4fc75dbcbeb2c5210d997b7bd98e97f6224b5ac6 --- /dev/null +++ b/packages/PrintSpooler/res/values-zh-rCN/arrays.xml @@ -0,0 +1,37 @@ + + + + + + ROC_8K + ROC_16K + PRC_1 + PRC_2 + PRC_3 + PRC_4 + PRC_5 + PRC_6 + PRC_7 + PRC_8 + PRC_9 + PRC_10 + PRC_16K + OM_PA_KAI + OM_DAI_PA_KAI + OM_JUURO_KU_KAI + + + diff --git a/packages/PrintSpooler/res/values-zh-rCN/donottranslate.xml b/packages/PrintSpooler/res/values-zh-rCN/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..f069da3a09e5863c6ab58a3e18bdddfbb85919db --- /dev/null +++ b/packages/PrintSpooler/res/values-zh-rCN/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + PRC_9 + @string/mediasize_standard_china + + diff --git a/packages/PrintSpooler/res/values/arrays.xml b/packages/PrintSpooler/res/values/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..afe3c7122dd79f62d2dac7959efd8587e1cfb252 --- /dev/null +++ b/packages/PrintSpooler/res/values/arrays.xml @@ -0,0 +1,158 @@ + + + + + + + NA_LETTER + @string/mediasize_standard_north_america + NA_GOVT_LETTER + @string/mediasize_standard_north_america + NA_LEGAL + @string/mediasize_standard_north_america + NA_JUNIOR_LEGAL + @string/mediasize_standard_north_america + NA_LEDGER + @string/mediasize_standard_north_america + NA_TABLOID + @string/mediasize_standard_north_america + NA_INDEX_3X5 + @string/mediasize_standard_north_america + NA_INDEX_4X6 + @string/mediasize_standard_north_america + NA_INDEX_5X8 + @string/mediasize_standard_north_america + NA_MONARCH + @string/mediasize_standard_north_america + NA_QUARTO + @string/mediasize_standard_north_america + NA_FOOLSCAP + @string/mediasize_standard_north_america + + + ROC_8K + @string/mediasize_standard_china + ROC_16K + @string/mediasize_standard_china + PRC_1 + @string/mediasize_standard_china + PRC_2 + @string/mediasize_standard_china + PRC_3 + @string/mediasize_standard_china + PRC_4 + @string/mediasize_standard_china + PRC_5 + @string/mediasize_standard_china + PRC_6 + @string/mediasize_standard_china + PRC_7 + @string/mediasize_standard_china + PRC_8 + @string/mediasize_standard_china + PRC_9 + @string/mediasize_standard_china + PRC_10 + @string/mediasize_standard_china + PRC_16K + @string/mediasize_standard_china + OM_PA_KAI + @string/mediasize_standard_china + OM_DAI_PA_KAI + @string/mediasize_standard_china + OM_JUURO_KU_KAI + @string/mediasize_standard_china + + + JIS_B10 + @string/mediasize_standard_japan + JIS_B9 + @string/mediasize_standard_japan + JIS_B8 + @string/mediasize_standard_japan + JIS_B7 + @string/mediasize_standard_japan + JIS_B6 + @string/mediasize_standard_japan + JIS_B5 + @string/mediasize_standard_japan + JIS_B4 + @string/mediasize_standard_japan + JIS_B3 + @string/mediasize_standard_japan + JIS_B2 + @string/mediasize_standard_japan + JIS_B1 + @string/mediasize_standard_japan + JIS_B0 + @string/mediasize_standard_japan + JIS_EXEC + @string/mediasize_standard_japan + JPN_CHOU4 + @string/mediasize_standard_japan + JPN_CHOU3 + @string/mediasize_standard_japan + JPN_CHOU2 + @string/mediasize_standard_japan + JPN_HAGAKI + @string/mediasize_standard_japan + JPN_OUFUKU + @string/mediasize_standard_japan + JPN_KAHU + @string/mediasize_standard_japan + JPN_KAKU2 + @string/mediasize_standard_japan + JPN_YOU4 + @string/mediasize_standard_japan + + + + + + ISO_A0 + ISO_A1 + ISO_A2 + ISO_A3 + ISO_A4 + ISO_A5 + ISO_A6 + ISO_A7 + ISO_A8 + ISO_A9 + ISO_A10 + ISO_B1 + ISO_B2 + ISO_B3 + ISO_B4 + ISO_B5 + ISO_B6 + ISO_B7 + ISO_B8 + ISO_B9 + ISO_B10 + ISO_C1 + ISO_C2 + ISO_C3 + ISO_C4 + ISO_C5 + ISO_C6 + ISO_C7 + ISO_C8 + ISO_C9 + ISO_C10 + + + diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml new file mode 100644 index 0000000000000000000000000000000000000000..9972c9600583b9f505fe540ab13f60f00fa39183 --- /dev/null +++ b/packages/PrintSpooler/res/values/colors.xml @@ -0,0 +1,25 @@ + + + + + + #FFFFFF + #333333 + #888888 + #CCCCCC + #F2F2F2 + + \ No newline at end of file diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml new file mode 100644 index 0000000000000000000000000000000000000000..e5a9d5d2768c594ae7768f7fcb1d62f2c58e0826 --- /dev/null +++ b/packages/PrintSpooler/res/values/constants.xml @@ -0,0 +1,29 @@ + + + + + + 0 + 1 + + + @integer/page_option_value_all + @integer/page_option_value_page_range + + + 400dip + + \ No newline at end of file diff --git a/packages/PrintSpooler/res/values/donottranslate.xml b/packages/PrintSpooler/res/values/donottranslate.xml new file mode 100644 index 0000000000000000000000000000000000000000..8069a1da89b32abaa0eeafe5ba6c8c61f50c2a07 --- /dev/null +++ b/packages/PrintSpooler/res/values/donottranslate.xml @@ -0,0 +1,28 @@ + + + + + + 0 + 1 + 2 + 3 + + + ISO_A4 + @string/mediasize_standard_iso + + diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..d74b4145dbff47e4c7519b95fb9cc67beb661773 --- /dev/null +++ b/packages/PrintSpooler/res/values/strings.xml @@ -0,0 +1,194 @@ + + + + + + + Print Spooler + + + Print + + + Save + + + Destination + + + Copies + + + Paper Size + + + Color + + + Orientation + + + Pages (%1$s) + + + e.g. 1—5,8,11—13 + + + Print preview + + + Install PDF viewer for preview + + + Printing app crashed + + + Pages + + + Generating print job + + + Save as PDF + + + All printers… + + + Print dialog + + + + + Search + + + All printers + + + Add service + + + Search box shown + + + Search box hidden + + + Add printer + + + + %1$s printer found + %1$s printers found + + + + + + Choose print service + + + Searching for printers + + + No printers found + + + + + Printing %1$s + + + Cancelling %1$s + + + Printer error %1$s + + + Printer blocked %1$s + + + + %1$d print job + %1$d print jobs + + + + Cancel + + + Restart + + + No connection to printer + + + unknown + + + %1$s – unavailable + + + Couldn\'t generate print job + + + + + + + Black & White + + Color + + + + + + Portrait + + Landscape + + + + + + All + + Range + + + + + + access all print jobs + + Allows the holder to access + print jobs created by another app. Should never be needed for normal apps. + + + start print + service configuration activities + + Allows the + holder to start the configuration activities of a print service. Should never be needed + for normal apps. + + diff --git a/packages/PrintSpooler/res/values/styles.xml b/packages/PrintSpooler/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..d64380aca4ffcd50b16d0a21e6d7cb764052fc0b --- /dev/null +++ b/packages/PrintSpooler/res/values/styles.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml new file mode 100644 index 0000000000000000000000000000000000000000..86f4a37161108380b5f3c6c6d691d9e77cce01be --- /dev/null +++ b/packages/PrintSpooler/res/values/themes.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..060146750a5510f8931f1164f75ae9f395c4020e --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java @@ -0,0 +1,627 @@ +/* + * 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.printspooler; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Loader; +import android.content.pm.ServiceInfo; +import android.os.AsyncTask; +import android.print.PrintManager; +import android.print.PrinterDiscoverySession; +import android.print.PrinterDiscoverySession.OnPrintersChangeListener; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.printservice.PrintServiceInfo; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import libcore.io.IoUtils; + +/** + * This class is responsible for loading printers by doing discovery + * and merging the discovered printers with the previously used ones. + */ +public class FusedPrintersProvider extends Loader> { + private static final String LOG_TAG = "FusedPrintersProvider"; + + private static final boolean DEBUG = false; + + private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f; + private static final int MAX_HISTORY_LENGTH = 50; + + private static final int MAX_FAVORITE_PRINTER_COUNT = 4; + + private final List mPrinters = + new ArrayList(); + + private final List mFavoritePrinters = + new ArrayList(); + + private final PersistenceManager mPersistenceManager; + + private PrinterDiscoverySession mDiscoverySession; + + private PrinterId mTrackedPrinter; + + public FusedPrintersProvider(Context context) { + super(context); + mPersistenceManager = new PersistenceManager(context); + } + + public void addHistoricalPrinter(PrinterInfo printer) { + mPersistenceManager.addPrinterAndWritePrinterHistory(printer); + } + + private void computeAndDeliverResult(Map discoveredPrinters) { + List printers = new ArrayList(); + + // Add the updated favorite printers. + final int favoritePrinterCount = mFavoritePrinters.size(); + for (int i = 0; i < favoritePrinterCount; i++) { + PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + PrinterInfo updatedPrinter = discoveredPrinters.remove( + favoritePrinter.getId()); + if (updatedPrinter != null) { + printers.add(updatedPrinter); + } else { + printers.add(favoritePrinter); + } + } + + // Add other updated printers. + final int printerCount = mPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = mPrinters.get(i); + PrinterInfo updatedPrinter = discoveredPrinters.remove( + printer.getId()); + if (updatedPrinter != null) { + printers.add(updatedPrinter); + } + } + + // Add the new printers, i.e. what is left. + printers.addAll(discoveredPrinters.values()); + + // Update the list of printers. + mPrinters.clear(); + mPrinters.addAll(printers); + + if (isStarted()) { + // Deliver the printers. + deliverResult(printers); + } + } + + @Override + protected void onStartLoading() { + if (DEBUG) { + Log.i(LOG_TAG, "onStartLoading() " + FusedPrintersProvider.this.hashCode()); + } + // The contract is that if we already have a valid, + // result the we have to deliver it immediately. + if (!mPrinters.isEmpty()) { + deliverResult(new ArrayList(mPrinters)); + } + // Always load the data to ensure discovery period is + // started and to make sure obsolete printers are updated. + onForceLoad(); + } + + @Override + protected void onStopLoading() { + if (DEBUG) { + Log.i(LOG_TAG, "onStopLoading() " + FusedPrintersProvider.this.hashCode()); + } + onCancelLoad(); + } + + @Override + protected void onForceLoad() { + if (DEBUG) { + Log.i(LOG_TAG, "onForceLoad() " + FusedPrintersProvider.this.hashCode()); + } + loadInternal(); + } + + private void loadInternal() { + if (mDiscoverySession == null) { + PrintManager printManager = (PrintManager) getContext() + .getSystemService(Context.PRINT_SERVICE); + mDiscoverySession = printManager.createPrinterDiscoverySession(); + mPersistenceManager.readPrinterHistory(); + } + if (mPersistenceManager.isReadHistoryCompleted() + && !mDiscoverySession.isPrinterDiscoveryStarted()) { + mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() { + @Override + public void onPrintersChanged() { + if (DEBUG) { + Log.i(LOG_TAG, "onPrintersChanged() count:" + + mDiscoverySession.getPrinters().size() + + " " + FusedPrintersProvider.this.hashCode()); + } + updatePrinters(mDiscoverySession.getPrinters()); + } + }); + final int favoriteCount = mFavoritePrinters.size(); + List printerIds = new ArrayList(favoriteCount); + for (int i = 0; i < favoriteCount; i++) { + printerIds.add(mFavoritePrinters.get(i).getId()); + } + mDiscoverySession.startPrinterDisovery(printerIds); + List printers = mDiscoverySession.getPrinters(); + if (!printers.isEmpty()) { + updatePrinters(printers); + } + } + } + + private void updatePrinters(List printers) { + if (mPrinters.equals(printers)) { + return; + } + ArrayMap printersMap = + new ArrayMap(); + final int printerCount = printers.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = printers.get(i); + printersMap.put(printer.getId(), printer); + } + computeAndDeliverResult(printersMap); + } + + @Override + protected boolean onCancelLoad() { + if (DEBUG) { + Log.i(LOG_TAG, "onCancelLoad() " + FusedPrintersProvider.this.hashCode()); + } + return cancelInternal(); + } + + private boolean cancelInternal() { + if (mDiscoverySession != null + && mDiscoverySession.isPrinterDiscoveryStarted()) { + if (mTrackedPrinter != null) { + mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter); + mTrackedPrinter = null; + } + mDiscoverySession.stopPrinterDiscovery(); + return true; + } else if (mPersistenceManager.isReadHistoryInProgress()) { + return mPersistenceManager.stopReadPrinterHistory(); + } + return false; + } + + @Override + protected void onReset() { + if (DEBUG) { + Log.i(LOG_TAG, "onReset() " + FusedPrintersProvider.this.hashCode()); + } + onStopLoading(); + mPrinters.clear(); + if (mDiscoverySession != null) { + mDiscoverySession.destroy(); + mDiscoverySession = null; + } + } + + @Override + protected void onAbandon() { + if (DEBUG) { + Log.i(LOG_TAG, "onAbandon() " + FusedPrintersProvider.this.hashCode()); + } + onStopLoading(); + } + + public void setTrackedPrinter(PrinterId printerId) { + if (isStarted() && mDiscoverySession != null + && mDiscoverySession.isPrinterDiscoveryStarted()) { + if (mTrackedPrinter != null) { + if (mTrackedPrinter.equals(printerId)) { + return; + } + mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter); + } + mTrackedPrinter = printerId; + mDiscoverySession.startPrinterStateTracking(printerId); + } + } + + private final class PersistenceManager { + private static final String PERSIST_FILE_NAME = "printer_history.xml"; + + private static final String TAG_PRINTERS = "printers"; + + private static final String TAG_PRINTER = "printer"; + private static final String TAG_PRINTER_ID = "printerId"; + + private static final String ATTR_LOCAL_ID = "localId"; + private static final String ATTR_SERVICE_NAME = "serviceName"; + + private static final String ATTR_NAME = "name"; + private static final String ATTR_DESCRIPTION = "description"; + private static final String ATTR_STATUS = "status"; + + private final AtomicFile mStatePersistFile; + + private List mHistoricalPrinters; + + private boolean mReadHistoryCompleted; + private boolean mReadHistoryInProgress; + + private ReadTask mReadTask; + + private PersistenceManager(Context context) { + mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), + PERSIST_FILE_NAME)); + } + + public boolean isReadHistoryInProgress() { + return mReadHistoryInProgress; + } + + public boolean isReadHistoryCompleted() { + return mReadHistoryCompleted; + } + + public boolean stopReadPrinterHistory() { + final boolean cancelled = mReadTask.cancel(true); + mReadTask = null; + return cancelled; + } + + public void readPrinterHistory() { + if (DEBUG) { + Log.i(LOG_TAG, "read history started " + + FusedPrintersProvider.this.hashCode()); + } + mReadHistoryInProgress = true; + mReadTask = new ReadTask(); + mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + @SuppressWarnings("unchecked") + public void addPrinterAndWritePrinterHistory(PrinterInfo printer) { + if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) { + mHistoricalPrinters.remove(0); + } + mHistoricalPrinters.add(printer); + new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, + new ArrayList(mHistoricalPrinters)); + } + + private List computeFavoritePrinters(List printers) { + Map recordMap = + new ArrayMap(); + + // Recompute the weights. + float currentWeight = 1.0f; + final int printerCount = printers.size(); + for (int i = printerCount - 1; i >= 0; i--) { + PrinterInfo printer = printers.get(i); + // Aggregate weight for the same printer + PrinterRecord record = recordMap.get(printer.getId()); + if (record == null) { + record = new PrinterRecord(printer); + recordMap.put(printer.getId(), record); + } + record.weight += currentWeight; + currentWeight *= WEIGHT_DECAY_COEFFICIENT; + } + + // Soft the favorite printers. + List favoriteRecords = new ArrayList( + recordMap.values()); + Collections.sort(favoriteRecords); + + // Write the favorites to the output. + final int favoriteCount = Math.min(favoriteRecords.size(), + MAX_FAVORITE_PRINTER_COUNT); + List favoritePrinters = new ArrayList(favoriteCount); + for (int i = 0; i < favoriteCount; i++) { + PrinterInfo printer = favoriteRecords.get(i).printer; + favoritePrinters.add(printer); + } + + return favoritePrinters; + } + + private final class PrinterRecord implements Comparable { + public final PrinterInfo printer; + public float weight; + + public PrinterRecord(PrinterInfo printer) { + this.printer = printer; + } + + @Override + public int compareTo(PrinterRecord another) { + return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); + } + } + + private final class ReadTask extends AsyncTask> { + @Override + protected List doInBackground(Void... args) { + return doReadPrinterHistory(); + } + + @Override + protected void onPostExecute(List printers) { + if (DEBUG) { + Log.i(LOG_TAG, "read history completed " + + FusedPrintersProvider.this.hashCode()); + } + + // Ignore printer records whose target services are not enabled. + PrintManager printManager = (PrintManager) getContext() + .getSystemService(Context.PRINT_SERVICE); + List services = printManager + .getEnabledPrintServices(); + + Set enabledComponents = new ArraySet(); + final int installedServiceCount = services.size(); + for (int i = 0; i < installedServiceCount; i++) { + ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo; + ComponentName componentName = new ComponentName( + serviceInfo.packageName, serviceInfo.name); + enabledComponents.add(componentName); + } + + final int printerCount = printers.size(); + for (int i = printerCount - 1; i >= 0; i--) { + ComponentName printerServiceName = printers.get(i).getId().getServiceName(); + if (!enabledComponents.contains(printerServiceName)) { + printers.remove(i); + } + } + + // Store the filtered list. + mHistoricalPrinters = printers; + + // Compute the favorite printers. + mFavoritePrinters.clear(); + mFavoritePrinters.addAll(computeFavoritePrinters(mHistoricalPrinters)); + + mReadHistoryInProgress = false; + mReadHistoryCompleted = true; + + // Deliver the favorites. + Map discoveredPrinters = Collections.emptyMap(); + computeAndDeliverResult(discoveredPrinters); + + // Start loading the available printers. + loadInternal(); + + // We are done. + mReadTask = null; + } + + private List doReadPrinterHistory() { + FileInputStream in = null; + try { + in = mStatePersistFile.openRead(); + } catch (FileNotFoundException fnfe) { + if (DEBUG) { + Log.i(LOG_TAG, "No existing printer history " + + FusedPrintersProvider.this.hashCode()); + } + return new ArrayList(); + } + try { + List printers = new ArrayList(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseState(parser, printers); + return printers; + } catch (IllegalStateException ise) { + Slog.w(LOG_TAG, "Failed parsing ", ise); + } catch (NullPointerException npe) { + Slog.w(LOG_TAG, "Failed parsing ", npe); + } catch (NumberFormatException nfe) { + Slog.w(LOG_TAG, "Failed parsing ", nfe); + } catch (XmlPullParserException xppe) { + Slog.w(LOG_TAG, "Failed parsing ", xppe); + } catch (IOException ioe) { + Slog.w(LOG_TAG, "Failed parsing ", ioe); + } catch (IndexOutOfBoundsException iobe) { + Slog.w(LOG_TAG, "Failed parsing ", iobe); + } finally { + IoUtils.closeQuietly(in); + } + + return Collections.emptyList(); + } + + private void parseState(XmlPullParser parser, List outPrinters) + throws IOException, XmlPullParserException { + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS); + parser.next(); + + while (parsePrinter(parser, outPrinters)) { + // Be nice and respond to cancellation + if (isCancelled()) { + return; + } + parser.next(); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS); + } + + private boolean parsePrinter(XmlPullParser parser, List outPrinters) + throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) { + return false; + } + + String name = parser.getAttributeValue(null, ATTR_NAME); + String description = parser.getAttributeValue(null, ATTR_DESCRIPTION); + final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS)); + + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID); + String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID); + ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue( + null, ATTR_SERVICE_NAME)); + PrinterId printerId = new PrinterId(service, localId); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID); + parser.next(); + + PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status); + builder.setDescription(description); + PrinterInfo printer = builder.build(); + + outPrinters.add(printer); + + if (DEBUG) { + Log.i(LOG_TAG, "[RESTORED] " + printer); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTER); + + return true; + } + + private void expect(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (!accept(parser, type, tag)) { + throw new XmlPullParserException("Exepected event: " + type + + " and tag: " + tag + " but got event: " + parser.getEventType() + + " and tag:" + parser.getName()); + } + } + + private void skipEmptyTextTags(XmlPullParser parser) + throws IOException, XmlPullParserException { + while (accept(parser, XmlPullParser.TEXT, null) + && "\n".equals(parser.getText())) { + parser.next(); + } + } + + private boolean accept(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (parser.getEventType() != type) { + return false; + } + if (tag != null) { + if (!tag.equals(parser.getName())) { + return false; + } + } else if (parser.getName() != null) { + return false; + } + return true; + } + }; + + private final class WriteTask extends AsyncTask, Void, Void> { + @Override + protected Void doInBackground(List... printers) { + doWritePrinterHistory(printers[0]); + return null; + } + + private void doWritePrinterHistory(List printers) { + FileOutputStream out = null; + try { + out = mStatePersistFile.startWrite(); + + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_PRINTERS); + + final int printerCount = printers.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = printers.get(i); + + serializer.startTag(null, TAG_PRINTER); + + serializer.attribute(null, ATTR_NAME, printer.getName()); + // Historical printers are always stored as unavailable. + serializer.attribute(null, ATTR_STATUS, String.valueOf( + PrinterInfo.STATUS_UNAVAILABLE)); + String description = printer.getDescription(); + if (description != null) { + serializer.attribute(null, ATTR_DESCRIPTION, description); + } + + PrinterId printerId = printer.getId(); + serializer.startTag(null, TAG_PRINTER_ID); + serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId()); + serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName() + .flattenToString()); + serializer.endTag(null, TAG_PRINTER_ID); + + serializer.endTag(null, TAG_PRINTER); + + if (DEBUG) { + Log.i(LOG_TAG, "[PERSISTED] " + printer); + } + } + + serializer.endTag(null, TAG_PRINTERS); + serializer.endDocument(); + mStatePersistFile.finishWrite(out); + + if (DEBUG) { + Log.i(LOG_TAG, "[PERSIST END]"); + } + } catch (IOException ioe) { + Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe); + mStatePersistFile.failWrite(out); + } finally { + IoUtils.closeQuietly(out); + } + } + }; + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ac2756293e8c8fc8712ac6b309468b3525098c7a --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java @@ -0,0 +1,99 @@ +/* + * 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.printspooler; + +import android.content.Context; +import android.print.PrintAttributes.MediaSize; +import android.util.ArrayMap; + +import java.util.Comparator; +import java.util.Map; + +/** + * Utility functions and classes for dealing with media sizes. + */ +public class MediaSizeUtils { + + private static Map sMediaSizeToStandardMap; + + /** + * Gets the default media size for the current locale. + * + * @param context Context for accessing resources. + * @return The default media size. + */ + public static MediaSize getDefault(Context context) { + String mediaSizeId = context.getString(R.string.mediasize_default); + return MediaSize.getStandardMediaSizeById(mediaSizeId); + } + + private static String getStandardForMediaSize(Context context, MediaSize mediaSize) { + if (sMediaSizeToStandardMap == null) { + sMediaSizeToStandardMap = new ArrayMap(); + String[] mediaSizeToStandardMapValues = context.getResources() + .getStringArray(R.array.mediasize_to_standard_map); + final int mediaSizeToStandardCount = mediaSizeToStandardMapValues.length; + for (int i = 0; i < mediaSizeToStandardCount; i += 2) { + String mediaSizeId = mediaSizeToStandardMapValues[i]; + MediaSize key = MediaSize.getStandardMediaSizeById(mediaSizeId); + String value = mediaSizeToStandardMapValues[i + 1]; + sMediaSizeToStandardMap.put(key, value); + } + } + String standard = sMediaSizeToStandardMap.get(mediaSize); + return (standard != null) ? standard : context.getString( + R.string.mediasize_standard_iso); + } + + /** + * Comparator for ordering standard media sizes. The ones for the current + * standard go to the top and the ones for the other standards follow grouped + * by standard. Media sizes of the same standard are ordered alphabetically. + */ + public static final class MediaSizeComparator implements Comparator { + private final Context mContext; + + public MediaSizeComparator(Context context) { + mContext = context; + } + + @Override + public int compare(MediaSize lhs, MediaSize rhs) { + String currentStandard = mContext.getString(R.string.mediasize_standard); + String lhsStandard = getStandardForMediaSize(mContext, lhs); + String rhsStandard = getStandardForMediaSize(mContext, rhs); + + // The current standard always wins. + if (lhsStandard.equals(currentStandard)) { + if (!rhsStandard.equals(currentStandard)) { + return -1; + } + } else if (rhsStandard.equals(currentStandard)) { + return 1; + } + + if (!lhsStandard.equals(rhsStandard)) { + // Different standards - use the standard ordering. + return lhsStandard.compareTo(rhsStandard); + } else { + // Same standard - sort alphabetically by label. + return lhs.getLabel(mContext.getPackageManager()). + compareTo(rhs.getLabel(mContext.getPackageManager())); + } + } + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java new file mode 100644 index 0000000000000000000000000000000000000000..968a8bf4e7175107e719eb5a237389183028fbe3 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java @@ -0,0 +1,389 @@ +/* + * 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.printspooler; + +import android.app.Notification; +import android.app.Notification.InboxStyle; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.print.IPrintManager; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.provider.Settings; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is responsible for updating the print notifications + * based on print job state transitions. + */ +public class NotificationController { + public static final boolean DEBUG = false; + + public static final String LOG_TAG = "NotificationController"; + + private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB"; + private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB"; + + private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID"; + private static final String EXTRA_PRINTJOB_LABEL = "EXTRA_PRINTJOB_LABEL"; + private static final String EXTRA_PRINTER_NAME = "EXTRA_PRINTER_NAME"; + + private final Context mContext; + private final NotificationManager mNotificationManager; + + public NotificationController(Context context) { + mContext = context; + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + public void onUpdateNotifications(List printJobs) { + List notifyPrintJobs = new ArrayList(); + + final int printJobCount = printJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = printJobs.get(i); + if (shouldNotifyForState(printJob.getState())) { + notifyPrintJobs.add(printJob); + } + } + + updateNotification(notifyPrintJobs); + } + + private void updateNotification(List printJobs) { + if (printJobs.size() <= 0) { + removeNotification(); + } else if (printJobs.size() == 1) { + createSimpleNotification(printJobs.get(0)); + } else { + createStackedNotification(printJobs); + } + } + + private void createSimpleNotification(PrintJobInfo printJob) { + switch (printJob.getState()) { + case PrintJobInfo.STATE_FAILED: { + createFailedNotification(printJob); + } break; + + case PrintJobInfo.STATE_BLOCKED: { + if (!printJob.isCancelling()) { + createBlockedNotification(printJob); + } else { + createCancellingNotification(printJob); + } + } break; + + default: { + if (!printJob.isCancelling()) { + createPrintingNotification(printJob); + } else { + createCancellingNotification(printJob); + } + } break; + } + } + + private void createPrintingNotification(PrintJobInfo printJob) { + Notification.Builder builder = new Notification.Builder(mContext) + .setContentIntent(createContentIntent(printJob.getId())) + .setSmallIcon(computeNotificationIcon(printJob)) + .setContentTitle(computeNotificationTitle(printJob)) + .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel), + createCancelIntent(printJob)) + .setContentText(printJob.getPrinterName()) + .setWhen(System.currentTimeMillis()) + .setOngoing(true) + .setShowWhen(true); + mNotificationManager.notify(0, builder.build()); + } + + private void createFailedNotification(PrintJobInfo printJob) { + Notification.Builder builder = new Notification.Builder(mContext) + .setContentIntent(createContentIntent(printJob.getId())) + .setSmallIcon(computeNotificationIcon(printJob)) + .setContentTitle(computeNotificationTitle(printJob)) + .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel), + createCancelIntent(printJob)) + .addAction(R.drawable.ic_restart, mContext.getString(R.string.restart), + createRestartIntent(printJob.getId())) + .setContentText(printJob.getPrinterName()) + .setWhen(System.currentTimeMillis()) + .setOngoing(true) + .setShowWhen(true); + mNotificationManager.notify(0, builder.build()); + } + + private void createBlockedNotification(PrintJobInfo printJob) { + Notification.Builder builder = new Notification.Builder(mContext) + .setContentIntent(createContentIntent(printJob.getId())) + .setSmallIcon(computeNotificationIcon(printJob)) + .setContentTitle(computeNotificationTitle(printJob)) + .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel), + createCancelIntent(printJob)) + .setContentText(printJob.getPrinterName()) + .setWhen(System.currentTimeMillis()) + .setOngoing(true) + .setShowWhen(true); + mNotificationManager.notify(0, builder.build()); + } + + private void createCancellingNotification(PrintJobInfo printJob) { + Notification.Builder builder = new Notification.Builder(mContext) + .setContentIntent(createContentIntent(printJob.getId())) + .setSmallIcon(computeNotificationIcon(printJob)) + .setContentTitle(computeNotificationTitle(printJob)) + .setContentText(printJob.getPrinterName()) + .setWhen(System.currentTimeMillis()) + .setOngoing(true) + .setShowWhen(true); + mNotificationManager.notify(0, builder.build()); + } + + private void createStackedNotification(List printJobs) { + Notification.Builder builder = new Notification.Builder(mContext) + .setContentIntent(createContentIntent(null)) + .setWhen(System.currentTimeMillis()) + .setOngoing(true) + .setShowWhen(true); + + final int printJobCount = printJobs.size(); + + InboxStyle inboxStyle = new InboxStyle(); + inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText( + R.plurals.composite_notification_title_template, + printJobCount).toString(), printJobCount)); + + for (int i = printJobCount - 1; i>= 0; i--) { + PrintJobInfo printJob = printJobs.get(i); + if (i == printJobCount - 1) { + builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable( + computeNotificationIcon(printJob))).getBitmap()); + builder.setSmallIcon(computeNotificationIcon(printJob)); + builder.setContentTitle(computeNotificationTitle(printJob)); + builder.setContentText(printJob.getPrinterName()); + } + inboxStyle.addLine(computeNotificationTitle(printJob)); + } + + builder.setNumber(printJobCount); + builder.setStyle(inboxStyle); + + mNotificationManager.notify(0, builder.build()); + } + + private String computeNotificationTitle(PrintJobInfo printJob) { + switch (printJob.getState()) { + case PrintJobInfo.STATE_FAILED: { + return mContext.getString(R.string.failed_notification_title_template, + printJob.getLabel()); + } + + case PrintJobInfo.STATE_BLOCKED: { + if (!printJob.isCancelling()) { + return mContext.getString(R.string.blocked_notification_title_template, + printJob.getLabel()); + } else { + return mContext.getString( + R.string.cancelling_notification_title_template, + printJob.getLabel()); + } + } + + default: { + if (!printJob.isCancelling()) { + return mContext.getString(R.string.printing_notification_title_template, + printJob.getLabel()); + } else { + return mContext.getString( + R.string.cancelling_notification_title_template, + printJob.getLabel()); + } + } + } + } + + private void removeNotification() { + mNotificationManager.cancel(0); + } + + private PendingIntent createContentIntent(PrintJobId printJobId) { + Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS); + if (printJobId != null) { + intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString()); + intent.setData(Uri.fromParts("printjob", printJobId.flattenToString(), null)); + } + return PendingIntent.getActivity(mContext, 0, intent, 0); + } + + private PendingIntent createCancelIntent(PrintJobInfo printJob) { + Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class); + intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + printJob.getId().flattenToString()); + intent.putExtra(EXTRA_PRINT_JOB_ID, printJob.getId()); + intent.putExtra(EXTRA_PRINTJOB_LABEL, printJob.getLabel()); + intent.putExtra(EXTRA_PRINTER_NAME, printJob.getPrinterName()); + return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT); + } + + private PendingIntent createRestartIntent(PrintJobId printJobId) { + Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class); + intent.setAction(INTENT_ACTION_RESTART_PRINTJOB + "_" + printJobId.flattenToString()); + intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId); + return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT); + } + + private static boolean shouldNotifyForState(int state) { + switch (state) { + case PrintJobInfo.STATE_QUEUED: + case PrintJobInfo.STATE_STARTED: + case PrintJobInfo.STATE_FAILED: + case PrintJobInfo.STATE_COMPLETED: + case PrintJobInfo.STATE_CANCELED: + case PrintJobInfo.STATE_BLOCKED: { + return true; + } + } + return false; + } + + private static int computeNotificationIcon(PrintJobInfo printJob) { + switch (printJob.getState()) { + case PrintJobInfo.STATE_FAILED: + case PrintJobInfo.STATE_BLOCKED: { + return com.android.internal.R.drawable.ic_print_error; + } + default: { + if (!printJob.isCancelling()) { + return com.android.internal.R.drawable.ic_print; + } else { + return R.drawable.stat_notify_cancelling; + } + } + } + } + + public static final class NotificationBroadcastReceiver extends BroadcastReceiver { + private static final String LOG_TAG = "NotificationBroadcastReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) { + PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID); + String printJobLabel = intent.getExtras().getString(EXTRA_PRINTJOB_LABEL); + String printerName = intent.getExtras().getString(EXTRA_PRINTER_NAME); + handleCancelPrintJob(context, printJobId, printJobLabel, printerName); + } else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) { + PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID); + handleRestartPrintJob(context, printJobId); + } + } + + private void handleCancelPrintJob(final Context context, final PrintJobId printJobId, + final String printJobLabel, final String printerName) { + if (DEBUG) { + Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId); + } + + // Call into the print manager service off the main thread since + // the print manager service may end up binding to the print spooler + // service which binding is handled on the main thread. + PowerManager powerManager = (PowerManager) + context.getSystemService(Context.POWER_SERVICE); + final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + LOG_TAG); + wakeLock.acquire(); + + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + // We need to request the cancellation to be done by the print + // manager service since it has to communicate with the managing + // print service to request the cancellation. Also we need the + // system service to be bound to the spooler since canceling a + // print job will trigger persistence of current jobs which is + // done on another thread and until it finishes the spooler has + // to be kept around. + try { + IPrintManager printManager = IPrintManager.Stub.asInterface( + ServiceManager.getService(Context.PRINT_SERVICE)); + printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY, + UserHandle.myUserId()); + } catch (RemoteException re) { + Log.i(LOG_TAG, "Error requestion print job cancellation", re); + } finally { + wakeLock.release(); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + private void handleRestartPrintJob(final Context context, final PrintJobId printJobId) { + if (DEBUG) { + Log.i(LOG_TAG, "handleRestartPrintJob() printJobId:" + printJobId); + } + + // Call into the print manager service off the main thread since + // the print manager service may end up binding to the print spooler + // service which binding is handled on the main thread. + PowerManager powerManager = (PowerManager) + context.getSystemService(Context.POWER_SERVICE); + final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + LOG_TAG); + wakeLock.acquire(); + + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + // We need to request the restart to be done by the print manager + // service since the latter must be bound to the spooler because + // restarting a print job will trigger persistence of current jobs + // which is done on another thread and until it finishes the spooler has + // to be kept around. + try { + IPrintManager printManager = IPrintManager.Stub.asInterface( + ServiceManager.getService(Context.PRINT_SERVICE)); + printManager.restartPrintJob(printJobId, PrintManager.APP_ID_ANY, + UserHandle.myUserId()); + } catch (RemoteException re) { + Log.i(LOG_TAG, "Error requestion print job restart", re); + } finally { + wakeLock.release(); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java b/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..c1c4d21567166699b6d8d9b8c414b8591da71d74 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java @@ -0,0 +1,69 @@ +/* + * 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.printspooler; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class PrintDialogFrame extends FrameLayout { + + public final int mMaxWidth; + + public int mHeight; + + public PrintDialogFrame(Context context, AttributeSet attrs) { + super(context, attrs); + mMaxWidth = context.getResources().getDimensionPixelSize( + R.dimen.print_dialog_frame_max_width_dip); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int measuredWidth = getMeasuredWidth(); + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + switch (widthMode) { + case MeasureSpec.UNSPECIFIED: { + measuredWidth = mMaxWidth; + } break; + + case MeasureSpec.AT_MOST: { + final int receivedWidth = MeasureSpec.getSize(widthMeasureSpec); + measuredWidth = Math.min(mMaxWidth, receivedWidth); + } break; + } + + mHeight = Math.max(mHeight, getMeasuredHeight()); + + int measuredHeight = getMeasuredHeight(); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + switch (heightMode) { + case MeasureSpec.UNSPECIFIED: { + measuredHeight = mHeight; + } break; + + case MeasureSpec.AT_MOST: { + final int receivedHeight = MeasureSpec.getSize(heightMeasureSpec); + measuredHeight = Math.min(mHeight, receivedHeight); + } break; + } + + setMeasuredDimension(measuredWidth, measuredHeight); + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2997707c8cab4cd2e0a6882556c81733304830b2 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -0,0 +1,2706 @@ +/* + * 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.printspooler; + +import android.app.Activity; +import android.app.Dialog; +import android.app.LoaderManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.Loader; +import android.content.ServiceConnection; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.print.ILayoutResultCallback; +import android.print.IPrintDocumentAdapter; +import android.print.IWriteResultCallback; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; +import android.print.PrintAttributes.Resolution; +import android.print.PrintDocumentAdapter; +import android.print.PrintDocumentInfo; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterCapabilitiesInfo; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.provider.DocumentsContract; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; +import android.text.TextWatcher; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +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.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewPropertyAnimator; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; + +import com.android.printspooler.MediaSizeUtils.MediaSizeComparator; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Activity for configuring a print job. + */ +public class PrintJobConfigActivity extends Activity { + + private static final String LOG_TAG = "PrintJobConfigActivity"; + + private static final boolean DEBUG = false; + + public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; + + private static final int LOADER_ID_PRINTERS_LOADER = 1; + + private static final int ORIENTATION_PORTRAIT = 0; + private static final int ORIENTATION_LANDSCAPE = 1; + + private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9; + + private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE; + private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1; + + private static final int ACTIVITY_REQUEST_CREATE_FILE = 1; + private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2; + + private static final int CONTROLLER_STATE_FINISHED = 1; + private static final int CONTROLLER_STATE_FAILED = 2; + private static final int CONTROLLER_STATE_CANCELLED = 3; + private static final int CONTROLLER_STATE_INITIALIZED = 4; + private static final int CONTROLLER_STATE_STARTED = 5; + private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6; + private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7; + private static final int CONTROLLER_STATE_WRITE_STARTED = 8; + private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9; + + private static final int EDITOR_STATE_INITIALIZED = 1; + private static final int EDITOR_STATE_CONFIRMED_PRINT = 2; + private static final int EDITOR_STATE_CANCELLED = 3; + + private static final int MIN_COPIES = 1; + private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES); + + private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+"); + + private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile( + "(?=[]\\[+&|!(){}^\"~*?:\\\\])"); + + private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile( + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])" + + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+"); + + public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES}; + + private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().build(); + private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().build(); + + private final DeathRecipient mDeathRecipient = new DeathRecipient() { + @Override + public void binderDied() { + finish(); + } + }; + + private Editor mEditor; + private Document mDocument; + private PrintController mController; + + private PrintJobId mPrintJobId; + + private IBinder mIPrintDocumentAdapter; + + private Dialog mGeneratingPrintJobDialog; + + private PrintSpoolerProvider mSpoolerProvider; + + private String mCallingPackageName; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + + setTitle(R.string.print_dialog); + + Bundle extras = getIntent().getExtras(); + + PrintJobInfo printJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB); + if (printJob == null) { + throw new IllegalArgumentException("printJob cannot be null"); + } + + mPrintJobId = printJob.getId(); + mIPrintDocumentAdapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER); + if (mIPrintDocumentAdapter == null) { + throw new IllegalArgumentException("PrintDocumentAdapter cannot be null"); + } + + PrintAttributes attributes = printJob.getAttributes(); + if (attributes != null) { + mCurrPrintAttributes.copyFrom(attributes); + } + + mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME); + + setContentView(R.layout.print_job_config_activity_container); + + try { + mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0); + } catch (RemoteException re) { + finish(); + return; + } + + mDocument = new Document(); + mEditor = new Editor(); + + mSpoolerProvider = new PrintSpoolerProvider(this, + new Runnable() { + @Override + public void run() { + // We got the spooler so unleash the UI. + mController = new PrintController(new RemotePrintDocumentAdapter( + IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter), + mSpoolerProvider.getSpooler().generateFileForPrintJob(mPrintJobId))); + mController.initialize(); + + mEditor.initialize(); + mEditor.postCreate(); + } + }); + } + + @Override + public void onResume() { + super.onResume(); + if (mSpoolerProvider.getSpooler() != null) { + mEditor.refreshCurrentPrinter(); + } + } + + @Override + protected void onDestroy() { + // We can safely do the work in here since at this point + // the system is bound to our (spooler) process which + // guarantees that this process will not be killed. + if (mController.hasStarted()) { + mController.finish(); + } + if (mEditor.isPrintConfirmed() && mController.isFinished()) { + mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_QUEUED, null); + } else { + mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_CANCELED, null); + } + mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); + if (mGeneratingPrintJobDialog != null) { + mGeneratingPrintJobDialog.dismiss(); + mGeneratingPrintJobDialog = null; + } + mSpoolerProvider.destroy(); + super.onDestroy(); + } + + public boolean onTouchEvent(MotionEvent event) { + if (!mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) { + if (!mController.isWorking()) { + PrintJobConfigActivity.this.finish(); + } + mEditor.cancel(); + return true; + } + return super.onTouchEvent(event); + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + event.startTracking(); + } + return super.onKeyDown(keyCode, event); + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (mEditor.isShwoingGeneratingPrintJobUi()) { + return true; + } + if (event.isTracking() && !event.isCanceled()) { + if (!mController.isWorking()) { + PrintJobConfigActivity.this.finish(); + } + } + mEditor.cancel(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + private boolean printAttributesChanged() { + return !mOldPrintAttributes.equals(mCurrPrintAttributes); + } + + private class PrintController { + private final AtomicInteger mRequestCounter = new AtomicInteger(); + + private final RemotePrintDocumentAdapter mRemotePrintAdapter; + + private final Bundle mMetadata; + + private final ControllerHandler mHandler; + + private final LayoutResultCallback mLayoutResultCallback; + + private final WriteResultCallback mWriteResultCallback; + + private int mControllerState = CONTROLLER_STATE_INITIALIZED; + + private boolean mHasStarted; + + private PageRange[] mRequestedPages; + + public PrintController(RemotePrintDocumentAdapter adapter) { + mRemotePrintAdapter = adapter; + mMetadata = new Bundle(); + mHandler = new ControllerHandler(getMainLooper()); + mLayoutResultCallback = new LayoutResultCallback(mHandler); + mWriteResultCallback = new WriteResultCallback(mHandler); + } + + public void initialize() { + mHasStarted = false; + mControllerState = CONTROLLER_STATE_INITIALIZED; + } + + public void cancel() { + mControllerState = CONTROLLER_STATE_CANCELLED; + } + + public boolean isCancelled() { + return (mControllerState == CONTROLLER_STATE_CANCELLED); + } + + public boolean isFinished() { + return (mControllerState == CONTROLLER_STATE_FINISHED); + } + + public boolean hasStarted() { + return mHasStarted; + } + + public boolean hasPerformedLayout() { + return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED; + } + + public boolean isPerformingLayout() { + return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED; + } + + public boolean isWorking() { + return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED + || mControllerState == CONTROLLER_STATE_WRITE_STARTED; + } + + public void start() { + mControllerState = CONTROLLER_STATE_STARTED; + mHasStarted = true; + mRemotePrintAdapter.start(); + } + + public void update() { + if (!mController.hasStarted()) { + mController.start(); + } + + // If the print attributes are the same and we are performing + // a layout, then we have to wait for it to completed which will + // trigger writing of the necessary pages. + final boolean printAttributesChanged = printAttributesChanged(); + if (!printAttributesChanged && isPerformingLayout()) { + return; + } + + // If print is confirmed we always do a layout since the previous + // ones were for preview and this one is for printing. + if (!printAttributesChanged && !mEditor.isPrintConfirmed()) { + if (mDocument.info == null) { + // We are waiting for the result of a layout, so do nothing. + return; + } + // If the attributes didn't change and we have done a layout, then + // we do not do a layout but may have to ask the app to write some + // pages. Hence, pretend layout completed and nothing changed, so + // we handle writing as usual. + handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get()); + } else { + mSpoolerProvider.getSpooler().setPrintJobAttributesNoPersistence( + mPrintJobId, mCurrPrintAttributes); + + mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, + !mEditor.isPrintConfirmed()); + + mControllerState = CONTROLLER_STATE_LAYOUT_STARTED; + + mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes, + mLayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet()); + + mOldPrintAttributes.copyFrom(mCurrPrintAttributes); + } + } + + public void finish() { + mControllerState = CONTROLLER_STATE_FINISHED; + mRemotePrintAdapter.finish(); + } + + private void handleOnLayoutFinished(PrintDocumentInfo info, + boolean layoutChanged, int sequence) { + if (mRequestCounter.get() != sequence) { + return; + } + + if (isCancelled()) { + mEditor.updateUi(); + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } + + mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED; + + // For layout purposes we care only whether the type or the page + // count changed. We still do not have the size since we did not + // call write. We use "layoutChanged" set by the application to + // know whether something else changed about the document. + final boolean infoChanged = !equalsIgnoreSize(info, mDocument.info); + // If the info changed, we update the document and the print job. + if (infoChanged) { + mDocument.info = info; + // Set the info. + mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence( + mPrintJobId, info); + } + + // If the document info or the layout changed, then + // drop the pages since we have to fetch them again. + if (infoChanged || layoutChanged) { + mDocument.pages = null; + mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence( + mPrintJobId, null); + } + + // No pages means that the user selected an invalid range while we + // were doing a layout or the layout returned a document info for + // which the selected range is invalid. In such a case we do not + // write anything and wait for the user to fix the range which will + // trigger an update. + mRequestedPages = mEditor.getRequestedPages(); + if (mRequestedPages == null || mRequestedPages.length == 0) { + mEditor.updateUi(); + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } else { + // If print is not confirmed we just ask for the first of the + // selected pages to emulate a behavior that shows preview + // increasing the chances that apps will implement the APIs + // correctly. + if (!mEditor.isPrintConfirmed()) { + if (ALL_PAGES_ARRAY.equals(mRequestedPages)) { + mRequestedPages = new PageRange[] {new PageRange(0, 0)}; + } else { + final int firstPage = mRequestedPages[0].getStart(); + mRequestedPages = new PageRange[] {new PageRange(firstPage, firstPage)}; + } + } + } + + // If the info and the layout did not change and we already have + // the requested pages, then nothing else to do. + if (!infoChanged && !layoutChanged + && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) { + // Nothing interesting changed and we have all requested pages. + // Then update the print jobs's pages as we will not do a write + // and we usually update the pages in the write complete callback. + updatePrintJobPages(mDocument.pages, mRequestedPages); + mEditor.updateUi(); + if (mEditor.isDone()) { + requestCreatePdfFileOrFinish(); + } + return; + } + + mEditor.updateUi(); + + // Request a write of the pages of interest. + mControllerState = CONTROLLER_STATE_WRITE_STARTED; + mRemotePrintAdapter.write(mRequestedPages, mWriteResultCallback, + mRequestCounter.incrementAndGet()); + } + + private void handleOnLayoutFailed(final CharSequence error, int sequence) { + if (mRequestCounter.get() != sequence) { + return; + } + mControllerState = CONTROLLER_STATE_FAILED; + mEditor.showUi(Editor.UI_ERROR, new Runnable() { + @Override + public void run() { + if (!TextUtils.isEmpty(error)) { + TextView messageView = (TextView) findViewById(R.id.message); + messageView.setText(error); + } + } + }); + } + + private void handleOnWriteFinished(PageRange[] pages, int sequence) { + if (mRequestCounter.get() != sequence) { + return; + } + + if (isCancelled()) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } + + mControllerState = CONTROLLER_STATE_WRITE_COMPLETED; + + // Update the document size. + File file = mSpoolerProvider.getSpooler() + .generateFileForPrintJob(mPrintJobId); + mDocument.info.setDataSize(file.length()); + + // Update the print job with the updated info. + mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence( + mPrintJobId, mDocument.info); + + // Update which pages we have fetched. + mDocument.pages = PageRangeUtils.normalize(pages); + + if (DEBUG) { + Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages) + + " and got: " + Arrays.toString(mDocument.pages)); + } + + updatePrintJobPages(mDocument.pages, mRequestedPages); + + if (mEditor.isDone()) { + requestCreatePdfFileOrFinish(); + } + } + + private void updatePrintJobPages(PageRange[] writtenPages, PageRange[] requestedPages) { + // Adjust the print job pages based on what was requested and written. + // The cases are ordered in the most expected to the least expected. + if (Arrays.equals(writtenPages, requestedPages)) { + // We got a document with exactly the pages we wanted. Hence, + // the printer has to print all pages in the data. + mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, + ALL_PAGES_ARRAY); + } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) { + // We requested specific pages but got all of them. Hence, + // the printer has to print only the requested pages. + mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, + requestedPages); + } else if (PageRangeUtils.contains(writtenPages, requestedPages)) { + // We requested specific pages and got more but not all pages. + // Hence, we have to offset appropriately the printed pages to + // be based off the start of the written ones instead of zero. + // The written pages are always non-null and not empty. + final int offset = -writtenPages[0].getStart(); + PageRange[] offsetPages = Arrays.copyOf(requestedPages, requestedPages.length); + PageRangeUtils.offset(offsetPages, offset); + mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, + offsetPages); + } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY) + && writtenPages.length == 1 && writtenPages[0].getStart() == 0 + && writtenPages[0].getEnd() == mDocument.info.getPageCount() - 1) { + // We requested all pages via the special constant and got all + // of them as an explicit enumeration. Hence, the printer has + // to print only the requested pages. + mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId, + writtenPages); + } else { + // We did not get the pages we requested, then the application + // misbehaves, so we fail quickly. + // TODO: We need some UI for announcing an error. + mControllerState = CONTROLLER_STATE_FAILED; + Log.e(LOG_TAG, "Received invalid pages from the app"); + mEditor.cancel(); + PrintJobConfigActivity.this.finish(); + } + } + + private void requestCreatePdfFileOrFinish() { + if (mEditor.isPrintingToPdf()) { + PrintJobInfo printJob = mSpoolerProvider.getSpooler() + .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.setType("application/pdf"); + intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel()); + intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName); + startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); + } else { + PrintJobConfigActivity.this.finish(); + } + } + + private void handleOnWriteFailed(final CharSequence error, int sequence) { + if (mRequestCounter.get() != sequence) { + return; + } + mControllerState = CONTROLLER_STATE_FAILED; + mEditor.showUi(Editor.UI_ERROR, new Runnable() { + @Override + public void run() { + if (!TextUtils.isEmpty(error)) { + TextView messageView = (TextView) findViewById(R.id.message); + messageView.setText(error); + } + } + }); + } + + private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) { + if (lhs == rhs) { + return true; + } + if (lhs == null) { + if (rhs != null) { + return false; + } + } else { + if (rhs == null) { + return false; + } + if (lhs.getContentType() != rhs.getContentType() + || lhs.getPageCount() != rhs.getPageCount()) { + return false; + } + } + return true; + } + + private final class ControllerHandler extends Handler { + public static final int MSG_ON_LAYOUT_FINISHED = 1; + public static final int MSG_ON_LAYOUT_FAILED = 2; + public static final int MSG_ON_WRITE_FINISHED = 3; + public static final int MSG_ON_WRITE_FAILED = 4; + + public ControllerHandler(Looper looper) { + super(looper, null, false); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_ON_LAYOUT_FINISHED: { + PrintDocumentInfo info = (PrintDocumentInfo) message.obj; + final boolean changed = (message.arg1 == 1); + final int sequence = message.arg2; + handleOnLayoutFinished(info, changed, sequence); + } break; + + case MSG_ON_LAYOUT_FAILED: { + CharSequence error = (CharSequence) message.obj; + final int sequence = message.arg1; + handleOnLayoutFailed(error, sequence); + } break; + + case MSG_ON_WRITE_FINISHED: { + PageRange[] pages = (PageRange[]) message.obj; + final int sequence = message.arg1; + handleOnWriteFinished(pages, sequence); + } break; + + case MSG_ON_WRITE_FAILED: { + CharSequence error = (CharSequence) message.obj; + final int sequence = message.arg1; + handleOnWriteFailed(error, sequence); + } break; + } + } + } + } + + private static final class LayoutResultCallback extends ILayoutResultCallback.Stub { + private final WeakReference mWeakHandler; + + public LayoutResultCallback(PrintController.ControllerHandler handler) { + mWeakHandler = new WeakReference(handler); + } + + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) { + Handler handler = mWeakHandler.get(); + if (handler != null) { + handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FINISHED, + changed ? 1 : 0, sequence, info).sendToTarget(); + } + } + + @Override + public void onLayoutFailed(CharSequence error, int sequence) { + Handler handler = mWeakHandler.get(); + if (handler != null) { + handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FAILED, + sequence, 0, error).sendToTarget(); + } + } + } + + private static final class WriteResultCallback extends IWriteResultCallback.Stub { + private final WeakReference mWeakHandler; + + public WriteResultCallback(PrintController.ControllerHandler handler) { + mWeakHandler = new WeakReference(handler); + } + + @Override + public void onWriteFinished(PageRange[] pages, int sequence) { + Handler handler = mWeakHandler.get(); + if (handler != null) { + handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FINISHED, + sequence, 0, pages).sendToTarget(); + } + } + + @Override + public void onWriteFailed(CharSequence error, int sequence) { + Handler handler = mWeakHandler.get(); + if (handler != null) { + handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FAILED, + sequence, 0, error).sendToTarget(); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case ACTIVITY_REQUEST_CREATE_FILE: { + if (data != null) { + Uri uri = data.getData(); + writePrintJobDataAndFinish(uri); + } else { + mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, + new Runnable() { + @Override + public void run() { + mEditor.initialize(); + mEditor.bindUi(); + mEditor.reselectCurrentPrinter(); + mEditor.updateUi(); + } + }); + } + } break; + + case ACTIVITY_REQUEST_SELECT_PRINTER: { + if (resultCode == RESULT_OK) { + PrinterId printerId = (PrinterId) data.getParcelableExtra( + INTENT_EXTRA_PRINTER_ID); + if (printerId != null) { + mEditor.ensurePrinterSelected(printerId); + break; + } + } + mEditor.ensureCurrentPrinterSelected(); + } break; + } + } + + private void writePrintJobDataAndFinish(final Uri uri) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + InputStream in = null; + OutputStream out = null; + try { + PrintJobInfo printJob = mSpoolerProvider.getSpooler() + .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); + if (printJob == null) { + return null; + } + File file = mSpoolerProvider.getSpooler() + .generateFileForPrintJob(mPrintJobId); + in = new FileInputStream(file); + out = getContentResolver().openOutputStream(uri); + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); + } + } catch (FileNotFoundException fnfe) { + Log.e(LOG_TAG, "Error writing print job data!", fnfe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing print job data!", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + } + return null; + } + + @Override + public void onPostExecute(Void result) { + mEditor.cancel(); + PrintJobConfigActivity.this.finish(); + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + private final class Editor { + private static final int UI_NONE = 0; + private static final int UI_EDITING_PRINT_JOB = 1; + private static final int UI_GENERATING_PRINT_JOB = 2; + private static final int UI_ERROR = 3; + + private EditText mCopiesEditText; + + private TextView mRangeOptionsTitle; + private TextView mPageRangeTitle; + private EditText mPageRangeEditText; + + private Spinner mDestinationSpinner; + private DestinationAdapter mDestinationSpinnerAdapter; + + private Spinner mMediaSizeSpinner; + private ArrayAdapter> mMediaSizeSpinnerAdapter; + + private Spinner mColorModeSpinner; + private ArrayAdapter> mColorModeSpinnerAdapter; + + private Spinner mOrientationSpinner; + private ArrayAdapter> mOrientationSpinnerAdapter; + + private Spinner mRangeOptionsSpinner; + private ArrayAdapter> mRangeOptionsSpinnerAdapter; + + private SimpleStringSplitter mStringCommaSplitter = + new SimpleStringSplitter(','); + + private View mContentContainer; + + private Button mPrintButton; + + private PrinterId mNextPrinterId; + + private PrinterInfo mCurrentPrinter; + + private MediaSizeComparator mMediaSizeComparator; + + private final OnFocusChangeListener mFocusListener = new OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + EditText editText = (EditText) view; + if (!TextUtils.isEmpty(editText.getText())) { + editText.setSelection(editText.getText().length()); + } + } + }; + + private final OnItemSelectedListener mOnItemSelectedListener = + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView spinner, View view, int position, long id) { + if (spinner == mDestinationSpinner) { + if (mIgnoreNextDestinationChange) { + mIgnoreNextDestinationChange = false; + return; + } + + if (position == AdapterView.INVALID_POSITION) { + updateUi(); + return; + } + + if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) { + startSelectPrinterActivity(); + return; + } + + mCapabilitiesTimeout.remove(); + + mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter + .getItem(position); + + mSpoolerProvider.getSpooler().setPrintJobPrinterNoPersistence( + mPrintJobId, mCurrentPrinter); + + if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) { + updateUi(); + return; + } + + PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities(); + if (capabilities == null) { + mCapabilitiesTimeout.post(); + updateUi(); + refreshCurrentPrinter(); + } else { + updatePrintAttributes(capabilities); + updateUi(); + mController.update(); + refreshCurrentPrinter(); + } + } else if (spinner == mMediaSizeSpinner) { + if (mOldMediaSizeSelectionIndex + == mMediaSizeSpinner.getSelectedItemPosition()) { + mOldMediaSizeSelectionIndex = AdapterView.INVALID_POSITION; + return; + } + SpinnerItem mediaItem = mMediaSizeSpinnerAdapter.getItem(position); + if (mOrientationSpinner.getSelectedItemPosition() == 0) { + mCurrPrintAttributes.setMediaSize(mediaItem.value.asPortrait()); + } else { + mCurrPrintAttributes.setMediaSize(mediaItem.value.asLandscape()); + } + if (!hasErrors()) { + mController.update(); + } + } else if (spinner == mColorModeSpinner) { + if (mOldColorModeSelectionIndex + == mColorModeSpinner.getSelectedItemPosition()) { + mOldColorModeSelectionIndex = AdapterView.INVALID_POSITION; + return; + } + SpinnerItem colorModeItem = + mColorModeSpinnerAdapter.getItem(position); + mCurrPrintAttributes.setColorMode(colorModeItem.value); + if (!hasErrors()) { + mController.update(); + } + } else if (spinner == mOrientationSpinner) { + if (mIgnoreNextOrientationChange) { + mIgnoreNextOrientationChange = false; + return; + } + SpinnerItem orientationItem = + mOrientationSpinnerAdapter.getItem(position); + setCurrentPrintAttributesOrientation(orientationItem.value); + if (!hasErrors()) { + mController.update(); + } + } else if (spinner == mRangeOptionsSpinner) { + if (mIgnoreNextRangeOptionChange) { + mIgnoreNextRangeOptionChange = false; + return; + } + updateUi(); + if (!hasErrors()) { + mController.update(); + } + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + /* do nothing*/ + } + }; + + private void setCurrentPrintAttributesOrientation(int orientation) { + MediaSize mediaSize = mCurrPrintAttributes.getMediaSize(); + if (orientation == ORIENTATION_PORTRAIT) { + if (!mediaSize.isPortrait()) { + // Rotate the media size. + mCurrPrintAttributes.setMediaSize(mediaSize.asPortrait()); + + // Rotate the resolution. + Resolution oldResolution = mCurrPrintAttributes.getResolution(); + Resolution newResolution = new Resolution( + oldResolution.getId(), + oldResolution.getLabel(), + oldResolution.getVerticalDpi(), + oldResolution.getHorizontalDpi()); + mCurrPrintAttributes.setResolution(newResolution); + + // Rotate the physical margins. + Margins oldMinMargins = mCurrPrintAttributes.getMinMargins(); + Margins newMinMargins = new Margins( + oldMinMargins.getBottomMils(), + oldMinMargins.getLeftMils(), + oldMinMargins.getTopMils(), + oldMinMargins.getRightMils()); + mCurrPrintAttributes.setMinMargins(newMinMargins); + } + } else { + if (mediaSize.isPortrait()) { + // Rotate the media size. + mCurrPrintAttributes.setMediaSize(mediaSize.asLandscape()); + + // Rotate the resolution. + Resolution oldResolution = mCurrPrintAttributes.getResolution(); + Resolution newResolution = new Resolution( + oldResolution.getId(), + oldResolution.getLabel(), + oldResolution.getVerticalDpi(), + oldResolution.getHorizontalDpi()); + mCurrPrintAttributes.setResolution(newResolution); + + // Rotate the physical margins. + Margins oldMinMargins = mCurrPrintAttributes.getMinMargins(); + Margins newMargins = new Margins( + oldMinMargins.getTopMils(), + oldMinMargins.getRightMils(), + oldMinMargins.getBottomMils(), + oldMinMargins.getLeftMils()); + mCurrPrintAttributes.setMinMargins(newMargins); + } + } + } + + private void updatePrintAttributes(PrinterCapabilitiesInfo capabilities) { + PrintAttributes defaults = capabilities.getDefaults(); + + // Sort the media sizes based on the current locale. + List sortedMediaSizes = new ArrayList( + capabilities.getMediaSizes()); + Collections.sort(sortedMediaSizes, mMediaSizeComparator); + + // Media size. + MediaSize currMediaSize = mCurrPrintAttributes.getMediaSize(); + if (currMediaSize == null) { + mCurrPrintAttributes.setMediaSize(defaults.getMediaSize()); + } else { + MediaSize currMediaSizePortrait = currMediaSize.asPortrait(); + final int mediaSizeCount = sortedMediaSizes.size(); + for (int i = 0; i < mediaSizeCount; i++) { + MediaSize mediaSize = sortedMediaSizes.get(i); + if (currMediaSizePortrait.equals(mediaSize.asPortrait())) { + mCurrPrintAttributes.setMediaSize(currMediaSize); + break; + } + } + } + + // Color mode. + final int colorMode = mCurrPrintAttributes.getColorMode(); + if ((capabilities.getColorModes() & colorMode) == 0) { + mCurrPrintAttributes.setColorMode(colorMode); + } + + // Resolution + Resolution resolution = mCurrPrintAttributes.getResolution(); + if (resolution == null || !capabilities.getResolutions().contains(resolution)) { + mCurrPrintAttributes.setResolution(defaults.getResolution()); + } + + // Margins. + Margins margins = mCurrPrintAttributes.getMinMargins(); + if (margins == null) { + mCurrPrintAttributes.setMinMargins(defaults.getMinMargins()); + } else { + Margins minMargins = capabilities.getMinMargins(); + if (margins.getLeftMils() < minMargins.getLeftMils() + || margins.getTopMils() < minMargins.getTopMils() + || margins.getRightMils() > minMargins.getRightMils() + || margins.getBottomMils() > minMargins.getBottomMils()) { + mCurrPrintAttributes.setMinMargins(defaults.getMinMargins()); + } + } + } + + private final TextWatcher mCopiesTextWatcher = new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + /* do nothing */ + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + /* do nothing */ + } + + @Override + public void afterTextChanged(Editable editable) { + if (mIgnoreNextCopiesChange) { + mIgnoreNextCopiesChange = false; + return; + } + + final boolean hadErrors = hasErrors(); + + if (editable.length() == 0) { + mCopiesEditText.setError(""); + updateUi(); + return; + } + + int copies = 0; + try { + copies = Integer.parseInt(editable.toString()); + } catch (NumberFormatException nfe) { + /* ignore */ + } + + if (copies < MIN_COPIES) { + mCopiesEditText.setError(""); + updateUi(); + return; + } + + mCopiesEditText.setError(null); + mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence( + mPrintJobId, copies); + updateUi(); + + if (hadErrors && !hasErrors() && printAttributesChanged()) { + mController.update(); + } + } + }; + + private final TextWatcher mRangeTextWatcher = new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + /* do nothing */ + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + /* do nothing */ + } + + @Override + public void afterTextChanged(Editable editable) { + if (mIgnoreNextRangeChange) { + mIgnoreNextRangeChange = false; + return; + } + + final boolean hadErrors = hasErrors(); + + String text = editable.toString(); + + if (TextUtils.isEmpty(text)) { + mPageRangeEditText.setError(""); + updateUi(); + return; + } + + String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////"); + if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) { + mPageRangeEditText.setError(""); + updateUi(); + return; + } + + // The range + Matcher matcher = PATTERN_DIGITS.matcher(text); + while (matcher.find()) { + String numericString = text.substring(matcher.start(), matcher.end()).trim(); + if (TextUtils.isEmpty(numericString)) { + continue; + } + final int pageIndex = Integer.parseInt(numericString); + if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) { + mPageRangeEditText.setError(""); + updateUi(); + return; + } + } + + // We intentionally do not catch the case of the from page being + // greater than the to page. When computing the requested pages + // we just swap them if necessary. + + mPageRangeEditText.setError(null); + mPrintButton.setEnabled(true); + updateUi(); + + if (hadErrors && !hasErrors() && printAttributesChanged()) { + updateUi(); + } + } + }; + + private final WaitForPrinterCapabilitiesTimeout mCapabilitiesTimeout = + new WaitForPrinterCapabilitiesTimeout(); + + private int mEditorState; + + private boolean mIgnoreNextDestinationChange; + private int mOldMediaSizeSelectionIndex; + private int mOldColorModeSelectionIndex; + private boolean mIgnoreNextOrientationChange; + private boolean mIgnoreNextRangeOptionChange; + private boolean mIgnoreNextCopiesChange; + private boolean mIgnoreNextRangeChange; + + private int mCurrentUi = UI_NONE; + + private boolean mFavoritePrinterSelected; + + public Editor() { + showUi(UI_EDITING_PRINT_JOB, null); + } + + public void postCreate() { + // Destination. + mMediaSizeComparator = new MediaSizeComparator(PrintJobConfigActivity.this); + mDestinationSpinnerAdapter = new DestinationAdapter(); + mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + // Initially, we have only safe to PDF as a printer but after some + // printers are loaded we want to select the user's favorite one + // which is the first. + if (!mFavoritePrinterSelected && mDestinationSpinnerAdapter.getCount() > 2) { + mFavoritePrinterSelected = true; + mDestinationSpinner.setSelection(0); + // Workaround again the weird spinner behavior to notify for selection + // change on the next layout pass as the current printer is used below. + mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter.getItem(0); + } + + // If there is a next printer to select and we succeed selecting + // it - done. Let the selection handling code make everything right. + if (mNextPrinterId != null && selectPrinter(mNextPrinterId)) { + mNextPrinterId = null; + return; + } + + // If the current printer properties changed, we update the UI. + if (mCurrentPrinter != null) { + final int printerCount = mDestinationSpinnerAdapter.getCount(); + for (int i = 0; i < printerCount; i++) { + Object item = mDestinationSpinnerAdapter.getItem(i); + // Some items are not printers + if (item instanceof PrinterInfo) { + PrinterInfo printer = (PrinterInfo) item; + if (!printer.getId().equals(mCurrentPrinter.getId())) { + continue; + } + + // If nothing changed - done. + if (mCurrentPrinter.equals(printer)) { + return; + } + + // If the current printer became available and has no + // capabilities, we refresh it. + if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE + && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE + && printer.getCapabilities() == null) { + if (!mCapabilitiesTimeout.isPosted()) { + mCapabilitiesTimeout.post(); + } + mCurrentPrinter.copyFrom(printer); + refreshCurrentPrinter(); + return; + } + + // If the current printer became unavailable or its + // capabilities go away, we update the UI and add a + // timeout to declare the printer as unavailable. + if ((mCurrentPrinter.getStatus() != PrinterInfo.STATUS_UNAVAILABLE + && printer.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) + || (mCurrentPrinter.getCapabilities() != null + && printer.getCapabilities() == null)) { + if (!mCapabilitiesTimeout.isPosted()) { + mCapabilitiesTimeout.post(); + } + mCurrentPrinter.copyFrom(printer); + updateUi(); + return; + } + + // We just refreshed the current printer. + if (printer.getCapabilities() != null + && mCapabilitiesTimeout.isPosted()) { + mCapabilitiesTimeout.remove(); + updatePrintAttributes(printer.getCapabilities()); + updateUi(); + mController.update(); + } + + // Update the UI if capabilities changed. + boolean capabilitiesChanged = false; + + if (mCurrentPrinter.getCapabilities() == null) { + if (printer.getCapabilities() != null) { + capabilitiesChanged = true; + } + } else if (!mCurrentPrinter.getCapabilities().equals( + printer.getCapabilities())) { + capabilitiesChanged = true; + } + + // Update the UI if the status changed. + final boolean statusChanged = mCurrentPrinter.getStatus() + != printer.getStatus(); + + // Update the printer with the latest info. + if (!mCurrentPrinter.equals(printer)) { + mCurrentPrinter.copyFrom(printer); + } + + if (capabilitiesChanged || statusChanged) { + // If something changed during update... + if (updateUi() || !mController.hasPerformedLayout()) { + // Update the document. + mController.update(); + } + } + + break; + } + } + } + } + + @Override + public void onInvalidated() { + /* do nothing - we always have one fake PDF printer */ + } + }); + + // Media size. + mMediaSizeSpinnerAdapter = new ArrayAdapter>( + PrintJobConfigActivity.this, + R.layout.spinner_dropdown_item, R.id.title); + + // Color mode. + mColorModeSpinnerAdapter = new ArrayAdapter>( + PrintJobConfigActivity.this, + R.layout.spinner_dropdown_item, R.id.title); + + // Orientation + mOrientationSpinnerAdapter = new ArrayAdapter>( + PrintJobConfigActivity.this, + R.layout.spinner_dropdown_item, R.id.title); + String[] orientationLabels = getResources().getStringArray( + R.array.orientation_labels); + mOrientationSpinnerAdapter.add(new SpinnerItem( + ORIENTATION_PORTRAIT, orientationLabels[0])); + mOrientationSpinnerAdapter.add(new SpinnerItem( + ORIENTATION_LANDSCAPE, orientationLabels[1])); + + // Range options + mRangeOptionsSpinnerAdapter = new ArrayAdapter>( + PrintJobConfigActivity.this, + R.layout.spinner_dropdown_item, R.id.title); + final int[] rangeOptionsValues = getResources().getIntArray( + R.array.page_options_values); + String[] rangeOptionsLabels = getResources().getStringArray( + R.array.page_options_labels); + final int rangeOptionsCount = rangeOptionsLabels.length; + for (int i = 0; i < rangeOptionsCount; i++) { + mRangeOptionsSpinnerAdapter.add(new SpinnerItem( + rangeOptionsValues[i], rangeOptionsLabels[i])); + } + + showUi(UI_EDITING_PRINT_JOB, null); + bindUi(); + updateUi(); + } + + public void reselectCurrentPrinter() { + if (mCurrentPrinter != null) { + // TODO: While the data did not change and we set the adapter + // to a newly inflated spinner, the latter does not show the + // current item unless we poke the adapter. This requires more + // investigation. Maybe an optimization in AdapterView does not + // call into the adapter if the view is not visible which is the + // case when we set the adapter. + mDestinationSpinnerAdapter.notifyDataSetChanged(); + final int position = mDestinationSpinnerAdapter.getPrinterIndex( + mCurrentPrinter.getId()); + mDestinationSpinner.setSelection(position); + } + } + + public void refreshCurrentPrinter() { + PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); + if (printer != null) { + FusedPrintersProvider printersLoader = (FusedPrintersProvider) + (Loader) getLoaderManager().getLoader( + LOADER_ID_PRINTERS_LOADER); + if (printersLoader != null) { + printersLoader.setTrackedPrinter(printer.getId()); + } + } + } + + public void addCurrentPrinterToHistory() { + PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); + PrinterId fakePdfPritnerId = mDestinationSpinnerAdapter.mFakePdfPrinter.getId(); + if (printer != null && !printer.getId().equals(fakePdfPritnerId)) { + FusedPrintersProvider printersLoader = (FusedPrintersProvider) + (Loader) getLoaderManager().getLoader( + LOADER_ID_PRINTERS_LOADER); + if (printersLoader != null) { + printersLoader.addHistoricalPrinter(printer); + } + } + } + + public void ensurePrinterSelected(PrinterId printerId) { + // If the printer is not present maybe the loader is not + // updated yet. In this case make a note and as soon as + // the printer appears will will select it. + if (!selectPrinter(printerId)) { + mNextPrinterId = printerId; + } + } + + public boolean selectPrinter(PrinterId printerId) { + mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId); + final int position = mDestinationSpinnerAdapter.getPrinterIndex(printerId); + if (position != AdapterView.INVALID_POSITION + && position != mDestinationSpinner.getSelectedItemPosition()) { + Object item = mDestinationSpinnerAdapter.getItem(position); + mCurrentPrinter = (PrinterInfo) item; + mDestinationSpinner.setSelection(position); + return true; + } + return false; + } + + public void ensureCurrentPrinterSelected() { + if (mCurrentPrinter != null) { + selectPrinter(mCurrentPrinter.getId()); + } + } + + public boolean isPrintingToPdf() { + return mDestinationSpinner.getSelectedItem() + == mDestinationSpinnerAdapter.mFakePdfPrinter; + } + + public boolean shouldCloseOnTouch(MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_DOWN) { + return false; + } + + final int[] locationInWindow = new int[2]; + mContentContainer.getLocationInWindow(locationInWindow); + + final int windowTouchSlop = ViewConfiguration.get(PrintJobConfigActivity.this) + .getScaledWindowTouchSlop(); + final int eventX = (int) event.getX(); + final int eventY = (int) event.getY(); + final int lenientWindowLeft = locationInWindow[0] - windowTouchSlop; + final int lenientWindowRight = lenientWindowLeft + mContentContainer.getWidth() + + windowTouchSlop; + final int lenientWindowTop = locationInWindow[1] - windowTouchSlop; + final int lenientWindowBottom = lenientWindowTop + mContentContainer.getHeight() + + windowTouchSlop; + + if (eventX < lenientWindowLeft || eventX > lenientWindowRight + || eventY < lenientWindowTop || eventY > lenientWindowBottom) { + return true; + } + return false; + } + + public boolean isShwoingGeneratingPrintJobUi() { + return (mCurrentUi == UI_GENERATING_PRINT_JOB); + } + + public void showUi(int ui, final Runnable postSwitchCallback) { + if (ui == UI_NONE) { + throw new IllegalStateException("cannot remove the ui"); + } + + if (mCurrentUi == ui) { + return; + } + + final int oldUi = mCurrentUi; + mCurrentUi = ui; + + switch (oldUi) { + case UI_NONE: { + switch (ui) { + case UI_EDITING_PRINT_JOB: { + doUiSwitch(R.layout.print_job_config_activity_content_editing); + registerPrintButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } break; + + case UI_GENERATING_PRINT_JOB: { + doUiSwitch(R.layout.print_job_config_activity_content_generating); + registerCancelButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } break; + } + } break; + + case UI_EDITING_PRINT_JOB: { + switch (ui) { + case UI_GENERATING_PRINT_JOB: { + animateUiSwitch(R.layout.print_job_config_activity_content_generating, + new Runnable() { + @Override + public void run() { + registerCancelButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } + }, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); + } break; + + case UI_ERROR: { + animateUiSwitch(R.layout.print_job_config_activity_content_error, + new Runnable() { + @Override + public void run() { + registerOkButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } + }, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); + } break; + } + } break; + + case UI_GENERATING_PRINT_JOB: { + switch (ui) { + case UI_EDITING_PRINT_JOB: { + animateUiSwitch(R.layout.print_job_config_activity_content_editing, + new Runnable() { + @Override + public void run() { + registerPrintButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } + }, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); + } break; + + case UI_ERROR: { + animateUiSwitch(R.layout.print_job_config_activity_content_error, + new Runnable() { + @Override + public void run() { + registerOkButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } + }, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); + } break; + } + } break; + + case UI_ERROR: { + switch (ui) { + case UI_EDITING_PRINT_JOB: { + animateUiSwitch(R.layout.print_job_config_activity_content_editing, + new Runnable() { + @Override + public void run() { + registerPrintButtonClickListener(); + if (postSwitchCallback != null) { + postSwitchCallback.run(); + } + } + }, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); + } break; + } + } break; + } + } + + private void registerPrintButtonClickListener() { + Button printButton = (Button) findViewById(R.id.print_button); + printButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); + if (printer != null) { + mEditor.confirmPrint(); + mController.update(); + if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) { + mEditor.refreshCurrentPrinter(); + } + } else { + mEditor.cancel(); + PrintJobConfigActivity.this.finish(); + } + } + }); + } + + private void registerCancelButtonClickListener() { + Button cancelButton = (Button) findViewById(R.id.cancel_button); + cancelButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (!mController.isWorking()) { + PrintJobConfigActivity.this.finish(); + } + mEditor.cancel(); + } + }); + } + + private void registerOkButtonClickListener() { + Button okButton = (Button) findViewById(R.id.ok_button); + okButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, new Runnable() { + @Override + public void run() { + // Start over with a clean slate. + mOldPrintAttributes.clear(); + mController.initialize(); + mEditor.initialize(); + mEditor.bindUi(); + mEditor.reselectCurrentPrinter(); + } + }); + } + }); + } + + private void doUiSwitch(int showLayoutId) { + ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container); + contentContainer.removeAllViews(); + getLayoutInflater().inflate(showLayoutId, contentContainer, true); + } + + private void animateUiSwitch(int showLayoutId, final Runnable beforeShowNewUiAction, + final LayoutParams containerParams) { + // Find everything we will shuffle around. + final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container); + final View hidingView = contentContainer.getChildAt(0); + final View showingView = getLayoutInflater().inflate(showLayoutId, + null, false); + + // First animation - fade out the old content. + AutoCancellingAnimator.animate(hidingView).alpha(0.0f) + .withLayer().withEndAction(new Runnable() { + @Override + public void run() { + hidingView.setVisibility(View.INVISIBLE); + + // Prepare the new content with correct size and alpha. + showingView.setMinimumWidth(contentContainer.getWidth()); + showingView.setAlpha(0.0f); + + // Compute how to much shrink /stretch the content. + final int widthSpec = MeasureSpec.makeMeasureSpec( + contentContainer.getWidth(), MeasureSpec.UNSPECIFIED); + final int heightSpec = MeasureSpec.makeMeasureSpec( + contentContainer.getHeight(), MeasureSpec.UNSPECIFIED); + showingView.measure(widthSpec, heightSpec); + final float scaleY = (float) showingView.getMeasuredHeight() + / (float) contentContainer.getHeight(); + + // Second animation - resize the container. + AutoCancellingAnimator.animate(contentContainer).scaleY(scaleY) + .withEndAction(new Runnable() { + @Override + public void run() { + // Swap the old and the new content. + contentContainer.removeAllViews(); + contentContainer.setScaleY(1.0f); + contentContainer.addView(showingView); + + contentContainer.setLayoutParams(containerParams); + + beforeShowNewUiAction.run(); + + // Third animation - show the new content. + AutoCancellingAnimator.animate(showingView).alpha(1.0f); + } + }); + } + }); + } + + public void initialize() { + mEditorState = EDITOR_STATE_INITIALIZED; + } + + public boolean isCancelled() { + return mEditorState == EDITOR_STATE_CANCELLED; + } + + public void cancel() { + mEditorState = EDITOR_STATE_CANCELLED; + mController.cancel(); + updateUi(); + } + + public boolean isDone() { + return isPrintConfirmed() || isCancelled(); + } + + public boolean isPrintConfirmed() { + return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; + } + + public void confirmPrint() { + addCurrentPrinterToHistory(); + mEditorState = EDITOR_STATE_CONFIRMED_PRINT; + showUi(UI_GENERATING_PRINT_JOB, null); + } + + public PageRange[] getRequestedPages() { + if (hasErrors()) { + return null; + } + if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { + List pageRanges = new ArrayList(); + mStringCommaSplitter.setString(mPageRangeEditText.getText().toString()); + + while (mStringCommaSplitter.hasNext()) { + String range = mStringCommaSplitter.next().trim(); + if (TextUtils.isEmpty(range)) { + continue; + } + final int dashIndex = range.indexOf('-'); + final int fromIndex; + final int toIndex; + + if (dashIndex > 0) { + fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1; + // It is possible that the dash is at the end since the input + // verification can has to allow the user to keep entering if + // this would lead to a valid input. So we handle this. + toIndex = (dashIndex < range.length() - 1) + ? Integer.parseInt(range.substring(dashIndex + 1, + range.length()).trim()) - 1 : fromIndex; + } else { + fromIndex = toIndex = Integer.parseInt(range) - 1; + } + + PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex), + Math.max(fromIndex, toIndex)); + pageRanges.add(pageRange); + } + + PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; + pageRanges.toArray(pageRangesArray); + + return PageRangeUtils.normalize(pageRangesArray); + } + + return ALL_PAGES_ARRAY; + } + + private void bindUi() { + if (mCurrentUi != UI_EDITING_PRINT_JOB) { + return; + } + + // Content container + mContentContainer = findViewById(R.id.content_container); + + // Copies + mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); + mCopiesEditText.setOnFocusChangeListener(mFocusListener); + mCopiesEditText.setText(MIN_COPIES_STRING); + mCopiesEditText.setSelection(mCopiesEditText.getText().length()); + mCopiesEditText.addTextChangedListener(mCopiesTextWatcher); + if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) { + mIgnoreNextCopiesChange = true; + } + mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence( + mPrintJobId, MIN_COPIES); + + // Destination. + mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner); + mDestinationSpinner.setDropDownWidth(ViewGroup.LayoutParams.MATCH_PARENT); + mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter); + mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mDestinationSpinnerAdapter.getCount() > 0) { + mIgnoreNextDestinationChange = true; + } + + // Media size. + mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner); + mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter); + mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mMediaSizeSpinnerAdapter.getCount() > 0) { + mOldMediaSizeSelectionIndex = 0; + } + + // Color mode. + mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner); + mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter); + mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mColorModeSpinnerAdapter.getCount() > 0) { + mOldColorModeSelectionIndex = 0; + } + + // Orientation + mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner); + mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter); + mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mOrientationSpinnerAdapter.getCount() > 0) { + mIgnoreNextOrientationChange = true; + } + + // Range options + mRangeOptionsTitle = (TextView) findViewById(R.id.range_options_title); + mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner); + mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter); + mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + if (mRangeOptionsSpinnerAdapter.getCount() > 0) { + mIgnoreNextRangeOptionChange = true; + } + + // Page range + mPageRangeTitle = (TextView) findViewById(R.id.page_range_title); + mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext); + mPageRangeEditText.setOnFocusChangeListener(mFocusListener); + mPageRangeEditText.addTextChangedListener(mRangeTextWatcher); + + // Print button + mPrintButton = (Button) findViewById(R.id.print_button); + registerPrintButtonClickListener(); + } + + public boolean updateUi() { + if (mCurrentUi != UI_EDITING_PRINT_JOB) { + return false; + } + if (isPrintConfirmed() || isCancelled()) { + mDestinationSpinner.setEnabled(false); + mCopiesEditText.setEnabled(false); + mMediaSizeSpinner.setEnabled(false); + mColorModeSpinner.setEnabled(false); + mOrientationSpinner.setEnabled(false); + mRangeOptionsSpinner.setEnabled(false); + mPageRangeEditText.setEnabled(false); + mPrintButton.setEnabled(false); + return false; + } + + // If a printer with capabilities is selected, then we enabled all options. + boolean allOptionsEnabled = false; + final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); + if (selectedIndex >= 0) { + Object item = mDestinationSpinnerAdapter.getItem(selectedIndex); + if (item instanceof PrinterInfo) { + PrinterInfo printer = (PrinterInfo) item; + if (printer.getCapabilities() != null + && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { + allOptionsEnabled = true; + } + } + } + + if (!allOptionsEnabled) { + mCopiesEditText.setEnabled(false); + mMediaSizeSpinner.setEnabled(false); + mColorModeSpinner.setEnabled(false); + mOrientationSpinner.setEnabled(false); + mRangeOptionsSpinner.setEnabled(false); + mPageRangeEditText.setEnabled(false); + mPrintButton.setEnabled(false); + return false; + } else { + boolean someAttributeSelectionChanged = false; + + PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); + PrinterCapabilitiesInfo capabilities = printer.getCapabilities(); + PrintAttributes defaultAttributes = printer.getCapabilities().getDefaults(); + + // Media size. + // Sort the media sizes based on the current locale. + List mediaSizes = new ArrayList(capabilities.getMediaSizes()); + Collections.sort(mediaSizes, mMediaSizeComparator); + + // If the media sizes changed, we update the adapter and the spinner. + boolean mediaSizesChanged = false; + final int mediaSizeCount = mediaSizes.size(); + if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) { + mediaSizesChanged = true; + } else { + for (int i = 0; i < mediaSizeCount; i++) { + if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) { + mediaSizesChanged = true; + break; + } + } + } + if (mediaSizesChanged) { + // Remember the old media size to try selecting it again. + int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION; + MediaSize oldMediaSize = mCurrPrintAttributes.getMediaSize(); + + // Rebuild the adapter data. + mMediaSizeSpinnerAdapter.clear(); + for (int i = 0; i < mediaSizeCount; i++) { + MediaSize mediaSize = mediaSizes.get(i); + if (mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) { + // Update the index of the old selection. + oldMediaSizeNewIndex = i; + } + mMediaSizeSpinnerAdapter.add(new SpinnerItem( + mediaSize, mediaSize.getLabel(getPackageManager()))); + } + + mMediaSizeSpinner.setEnabled(true); + + if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) { + // Select the old media size - nothing really changed. + setMediaSizeSpinnerSelectionNoCallback(oldMediaSizeNewIndex); + } else { + // Select the first or the default and mark if selection changed. + final int mediaSizeIndex = Math.max(mediaSizes.indexOf( + defaultAttributes.getMediaSize()), 0); + setMediaSizeSpinnerSelectionNoCallback(mediaSizeIndex); + if (oldMediaSize.isPortrait()) { + mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter + .getItem(mediaSizeIndex).value.asPortrait()); + } else { + mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter + .getItem(mediaSizeIndex).value.asLandscape()); + } + someAttributeSelectionChanged = true; + } + } + mMediaSizeSpinner.setEnabled(true); + + // Color mode. + final int colorModes = capabilities.getColorModes(); + + // If the color modes changed, we update the adapter and the spinner. + boolean colorModesChanged = false; + if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) { + colorModesChanged = true; + } else { + int remainingColorModes = colorModes; + int adapterIndex = 0; + while (remainingColorModes != 0) { + final int colorBitOffset = Integer.numberOfTrailingZeros( + remainingColorModes); + final int colorMode = 1 << colorBitOffset; + remainingColorModes &= ~colorMode; + if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) { + colorModesChanged = true; + break; + } + adapterIndex++; + } + } + if (colorModesChanged) { + // Remember the old color mode to try selecting it again. + int oldColorModeNewIndex = AdapterView.INVALID_POSITION; + final int oldColorMode = mCurrPrintAttributes.getColorMode(); + + // Rebuild the adapter data. + mColorModeSpinnerAdapter.clear(); + String[] colorModeLabels = getResources().getStringArray( + R.array.color_mode_labels); + int remainingColorModes = colorModes; + while (remainingColorModes != 0) { + final int colorBitOffset = Integer.numberOfTrailingZeros( + remainingColorModes); + final int colorMode = 1 << colorBitOffset; + if (colorMode == oldColorMode) { + // Update the index of the old selection. + oldColorModeNewIndex = colorBitOffset; + } + remainingColorModes &= ~colorMode; + mColorModeSpinnerAdapter.add(new SpinnerItem(colorMode, + colorModeLabels[colorBitOffset])); + } + mColorModeSpinner.setEnabled(true); + if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) { + // Select the old color mode - nothing really changed. + setColorModeSpinnerSelectionNoCallback(oldColorModeNewIndex); + } else { + final int selectedColorModeIndex = Integer.numberOfTrailingZeros( + (colorModes & defaultAttributes.getColorMode())); + setColorModeSpinnerSelectionNoCallback(selectedColorModeIndex); + mCurrPrintAttributes.setColorMode(mColorModeSpinnerAdapter + .getItem(selectedColorModeIndex).value); + someAttributeSelectionChanged = true; + } + } + mColorModeSpinner.setEnabled(true); + + // Orientation + MediaSize mediaSize = mCurrPrintAttributes.getMediaSize(); + if (mediaSize.isPortrait() + && mOrientationSpinner.getSelectedItemPosition() != 0) { + mIgnoreNextOrientationChange = true; + mOrientationSpinner.setSelection(0); + } else if (!mediaSize.isPortrait() + && mOrientationSpinner.getSelectedItemPosition() != 1) { + mIgnoreNextOrientationChange = true; + mOrientationSpinner.setSelection(1); + } + mOrientationSpinner.setEnabled(true); + + // Range options + PrintDocumentInfo info = mDocument.info; + if (info != null && info.getPageCount() > 0) { + if (info.getPageCount() == 1) { + mRangeOptionsSpinner.setEnabled(false); + } else { + mRangeOptionsSpinner.setEnabled(true); + if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { + if (!mPageRangeEditText.isEnabled()) { + mPageRangeEditText.setEnabled(true); + mPageRangeEditText.setVisibility(View.VISIBLE); + mPageRangeTitle.setVisibility(View.VISIBLE); + mPageRangeEditText.requestFocus(); + InputMethodManager imm = (InputMethodManager) + getSystemService(INPUT_METHOD_SERVICE); + imm.showSoftInput(mPageRangeEditText, 0); + } + } else { + mPageRangeEditText.setEnabled(false); + mPageRangeEditText.setVisibility(View.INVISIBLE); + mPageRangeTitle.setVisibility(View.INVISIBLE); + } + } + final int pageCount = mDocument.info.getPageCount(); + String title = (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) + ? getString(R.string.label_pages, String.valueOf(pageCount)) + : getString(R.string.page_count_unknown); + mRangeOptionsTitle.setText(title); + } else { + if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) { + mIgnoreNextRangeOptionChange = true; + mRangeOptionsSpinner.setSelection(0); + } + mRangeOptionsSpinner.setEnabled(false); + mRangeOptionsTitle.setText(getString(R.string.page_count_unknown)); + mPageRangeEditText.setEnabled(false); + mPageRangeEditText.setVisibility(View.INVISIBLE); + mPageRangeTitle.setVisibility(View.INVISIBLE); + } + + // Print/Print preview + if (mDestinationSpinner.getSelectedItemId() + != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) { + String newText = getString(R.string.print_button); + if (!TextUtils.equals(newText, mPrintButton.getText())) { + mPrintButton.setText(R.string.print_button); + } + } else { + String newText = getString(R.string.save_button); + if (!TextUtils.equals(newText, mPrintButton.getText())) { + mPrintButton.setText(R.string.save_button); + } + } + if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1 + && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors())) + || (mRangeOptionsSpinner.getSelectedItemPosition() == 0 + && (!mController.hasPerformedLayout() || hasErrors()))) { + mPrintButton.setEnabled(false); + } else { + mPrintButton.setEnabled(true); + } + + // Copies + if (mDestinationSpinner.getSelectedItemId() + != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) { + mCopiesEditText.setEnabled(true); + } else { + mCopiesEditText.setEnabled(false); + } + if (mCopiesEditText.getError() == null + && TextUtils.isEmpty(mCopiesEditText.getText())) { + mIgnoreNextCopiesChange = true; + mCopiesEditText.setText(String.valueOf(MIN_COPIES)); + mCopiesEditText.requestFocus(); + } + + return someAttributeSelectionChanged; + } + } + + private void setMediaSizeSpinnerSelectionNoCallback(int position) { + if (mMediaSizeSpinner.getSelectedItemPosition() != position) { + mOldMediaSizeSelectionIndex = position; + mMediaSizeSpinner.setSelection(position); + } + } + + private void setColorModeSpinnerSelectionNoCallback(int position) { + if (mColorModeSpinner.getSelectedItemPosition() != position) { + mOldColorModeSelectionIndex = position; + mColorModeSpinner.setSelection(position); + } + } + + private void startSelectPrinterActivity() { + Intent intent = new Intent(PrintJobConfigActivity.this, + SelectPrinterActivity.class); + startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER); + } + + private boolean hasErrors() { + if (mCopiesEditText.getError() != null) { + return true; + } + return mPageRangeEditText.getVisibility() == View.VISIBLE + && mPageRangeEditText.getError() != null; + } + + private final class SpinnerItem { + final T value; + CharSequence label; + + public SpinnerItem(T value, CharSequence label) { + this.value = value; + this.label = label; + } + + public String toString() { + return label.toString(); + } + } + + private final class WaitForPrinterCapabilitiesTimeout implements Runnable { + private static final long GET_CAPABILITIES_TIMEOUT_MILLIS = 10000; // 10sec + + private boolean mIsPosted; + + public void post() { + if (!mIsPosted) { + mDestinationSpinner.postDelayed(this, + GET_CAPABILITIES_TIMEOUT_MILLIS); + mIsPosted = true; + } + } + + public void remove() { + if (mIsPosted) { + mIsPosted = false; + mDestinationSpinner.removeCallbacks(this); + } + } + + public boolean isPosted() { + return mIsPosted; + } + + @Override + public void run() { + mIsPosted = false; + if (mDestinationSpinner.getSelectedItemPosition() >= 0) { + View itemView = mDestinationSpinner.getSelectedView(); + TextView titleView = (TextView) itemView.findViewById(R.id.subtitle); + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo( + mCurrentPrinter.getId().getServiceName().getPackageName(), 0); + CharSequence service = packageInfo.applicationInfo.loadLabel( + getPackageManager()); + String subtitle = getString(R.string.printer_unavailable, service.toString()); + titleView.setText(subtitle); + } catch (NameNotFoundException nnfe) { + /* ignore */ + } + } + } + } + + private final class DestinationAdapter extends BaseAdapter + implements LoaderManager.LoaderCallbacks>{ + private final List mPrinters = new ArrayList(); + + private PrinterInfo mFakePdfPrinter; + + public DestinationAdapter() { + getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this); + } + + public int getPrinterIndex(PrinterId printerId) { + for (int i = 0; i < getCount(); i++) { + PrinterInfo printer = (PrinterInfo) getItem(i); + if (printer != null && printer.getId().equals(printerId)) { + return i; + } + } + return AdapterView.INVALID_POSITION; + } + + public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) { + final int printerCount = mPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = (PrinterInfo) mPrinters.get(i); + if (printer.getId().equals(printerId)) { + // If already in the list - do nothing. + if (i < getCount() - 2) { + return; + } + // Else replace the last one (two items are not printers). + final int lastPrinterIndex = getCount() - 3; + mPrinters.set(i, mPrinters.get(lastPrinterIndex)); + mPrinters.set(lastPrinterIndex, printer); + notifyDataSetChanged(); + return; + } + } + } + + @Override + public int getCount() { + if (mFakePdfPrinter == null) { + return 0; + } + return Math.min(mPrinters.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT); + } + + @Override + public boolean isEnabled(int position) { + Object item = getItem(position); + if (item instanceof PrinterInfo) { + PrinterInfo printer = (PrinterInfo) item; + return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; + } + return true; + } + + @Override + public Object getItem(int position) { + if (mPrinters.isEmpty()) { + if (position == 0 && mFakePdfPrinter != null) { + return mFakePdfPrinter; + } + } else { + if (position < 1) { + return mPrinters.get(position); + } + if (position == 1 && mFakePdfPrinter != null) { + return mFakePdfPrinter; + } + if (position < getCount() - 1) { + return mPrinters.get(position - 1); + } + } + return null; + } + + @Override + public long getItemId(int position) { + if (mPrinters.isEmpty()) { + if (mFakePdfPrinter != null) { + if (position == 0) { + return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; + } else if (position == 1) { + return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; + } + } + } else { + if (position == 1 && mFakePdfPrinter != null) { + return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; + } + if (position == getCount() - 1) { + return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; + } + } + return position; + } + + @Override + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + View view = getView(position, convertView, parent); + view.setEnabled(isEnabled(position)); + return view; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = getLayoutInflater().inflate( + R.layout.printer_dropdown_item, parent, false); + } + + convertView.getLayoutParams().width = mDestinationSpinner.getWidth(); + + CharSequence title = null; + CharSequence subtitle = null; + Drawable icon = null; + + if (mPrinters.isEmpty()) { + if (position == 0 && mFakePdfPrinter != null) { + PrinterInfo printer = (PrinterInfo) getItem(position); + title = printer.getName(); + } else if (position == 1) { + title = getString(R.string.all_printers); + } + } else { + if (position == 1 && mFakePdfPrinter != null) { + PrinterInfo printer = (PrinterInfo) getItem(position); + title = printer.getName(); + } else if (position == getCount() - 1) { + title = getString(R.string.all_printers); + } else { + PrinterInfo printer = (PrinterInfo) getItem(position); + title = printer.getName(); + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo( + printer.getId().getServiceName().getPackageName(), 0); + subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager()); + icon = packageInfo.applicationInfo.loadIcon(getPackageManager()); + } catch (NameNotFoundException nnfe) { + /* ignore */ + } + } + } + + TextView titleView = (TextView) convertView.findViewById(R.id.title); + titleView.setText(title); + + TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); + if (!TextUtils.isEmpty(subtitle)) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(View.VISIBLE); + } else { + subtitleView.setText(null); + subtitleView.setVisibility(View.GONE); + } + + ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); + if (icon != null) { + iconView.setImageDrawable(icon); + iconView.setVisibility(View.VISIBLE); + } else { + iconView.setVisibility(View.GONE); + } + + return convertView; + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + if (id == LOADER_ID_PRINTERS_LOADER) { + return new FusedPrintersProvider(PrintJobConfigActivity.this); + } + return null; + } + + @Override + public void onLoadFinished(Loader> loader, + List printers) { + // If this is the first load, create the fake PDF printer. + // We do this to avoid flicker where the PDF printer is the + // only one and as soon as the loader loads the favorites + // it gets switched. Not a great user experience. + if (mFakePdfPrinter == null) { + mCurrentPrinter = mFakePdfPrinter = createFakePdfPrinter(); + updatePrintAttributes(mCurrentPrinter.getCapabilities()); + updateUi(); + } + + // We rearrange the printers if the user selects a printer + // not shown in the initial short list. Therefore, we have + // to keep the printer order. + + // No old printers - do not bother keeping their position. + if (mPrinters.isEmpty()) { + mPrinters.addAll(printers); + mEditor.ensureCurrentPrinterSelected(); + notifyDataSetChanged(); + return; + } + + // Add the new printers to a map. + ArrayMap newPrintersMap = + new ArrayMap(); + final int printerCount = printers.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = printers.get(i); + newPrintersMap.put(printer.getId(), printer); + } + + List newPrinters = new ArrayList(); + + // Update printers we already have. + final int oldPrinterCount = mPrinters.size(); + for (int i = 0; i < oldPrinterCount; i++) { + PrinterId oldPrinterId = mPrinters.get(i).getId(); + PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId); + if (updatedPrinter != null) { + newPrinters.add(updatedPrinter); + } + } + + // Add the rest of the new printers, i.e. what is left. + newPrinters.addAll(newPrintersMap.values()); + + mPrinters.clear(); + mPrinters.addAll(newPrinters); + + mEditor.ensureCurrentPrinterSelected(); + notifyDataSetChanged(); + } + + @Override + public void onLoaderReset(Loader> loader) { + mPrinters.clear(); + notifyDataSetInvalidated(); + } + + + private PrinterInfo createFakePdfPrinter() { + MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintJobConfigActivity.this); + + PrinterId printerId = new PrinterId(getComponentName(), "PDF printer"); + + PrinterCapabilitiesInfo.Builder builder = + new PrinterCapabilitiesInfo.Builder(printerId); + + String[] mediaSizeIds = getResources().getStringArray( + R.array.pdf_printer_media_sizes); + final int mediaSizeIdCount = mediaSizeIds.length; + for (int i = 0; i < mediaSizeIdCount; i++) { + String id = mediaSizeIds[i]; + MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id); + builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize)); + } + + builder.addResolution(new Resolution("PDF resolution", "PDF resolution", + 300, 300), true); + builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR + | PrintAttributes.COLOR_MODE_MONOCHROME, + PrintAttributes.COLOR_MODE_COLOR); + + return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf), + PrinterInfo.STATUS_IDLE) + .setCapabilities(builder.build()) + .build(); + } + } + } + + /** + * An instance of this class class is intended to be the first focusable + * in a layout to which the system automatically gives focus. It performs + * some voodoo to avoid the first tap on it to start an edit mode, rather + * to bring up the IME, i.e. to get the behavior as if the view was not + * focused. + */ + public static final class CustomEditText extends EditText { + private boolean mClickedBeforeFocus; + private CharSequence mError; + + public CustomEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean performClick() { + super.performClick(); + if (isFocused() && !mClickedBeforeFocus) { + clearFocus(); + requestFocus(); + } + mClickedBeforeFocus = true; + return true; + } + + @Override + public CharSequence getError() { + return mError; + } + + @Override + public void setError(CharSequence error, Drawable icon) { + setCompoundDrawables(null, null, icon, null); + mError = error; + } + + protected void onFocusChanged(boolean gainFocus, int direction, + Rect previouslyFocusedRect) { + if (!gainFocus) { + mClickedBeforeFocus = false; + } + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + } + } + + private static final class Document { + public PrintDocumentInfo info; + public PageRange[] pages; + } + + private static final class PageRangeUtils { + + private static final Comparator sComparator = new Comparator() { + @Override + public int compare(PageRange lhs, PageRange rhs) { + return lhs.getStart() - rhs.getStart(); + } + }; + + private PageRangeUtils() { + throw new UnsupportedOperationException(); + } + + public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges) { + if (ourRanges == null || otherRanges == null) { + return false; + } + + if (ourRanges.length == 1 + && PageRange.ALL_PAGES.equals(ourRanges[0])) { + return true; + } + + ourRanges = normalize(ourRanges); + otherRanges = normalize(otherRanges); + + // Note that the code below relies on the ranges being normalized + // which is they contain monotonically increasing non-intersecting + // subranges whose start is less that or equal to the end. + int otherRangeIdx = 0; + final int ourRangeCount = ourRanges.length; + final int otherRangeCount = otherRanges.length; + for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) { + PageRange ourRange = ourRanges[ourRangeIdx]; + for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) { + PageRange otherRange = otherRanges[otherRangeIdx]; + if (otherRange.getStart() > ourRange.getEnd()) { + break; + } + if (otherRange.getStart() < ourRange.getStart() + || otherRange.getEnd() > ourRange.getEnd()) { + return false; + } + } + } + if (otherRangeIdx < otherRangeCount) { + return false; + } + return true; + } + + public static PageRange[] normalize(PageRange[] pageRanges) { + if (pageRanges == null) { + return null; + } + final int oldRangeCount = pageRanges.length; + if (oldRangeCount <= 1) { + return pageRanges; + } + Arrays.sort(pageRanges, sComparator); + int newRangeCount = 1; + for (int i = 0; i < oldRangeCount - 1; i++) { + newRangeCount++; + PageRange currentRange = pageRanges[i]; + PageRange nextRange = pageRanges[i + 1]; + if (currentRange.getEnd() + 1 >= nextRange.getStart()) { + newRangeCount--; + pageRanges[i] = null; + pageRanges[i + 1] = new PageRange(currentRange.getStart(), + Math.max(currentRange.getEnd(), nextRange.getEnd())); + } + } + if (newRangeCount == oldRangeCount) { + return pageRanges; + } + return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount, + oldRangeCount); + } + + public static void offset(PageRange[] pageRanges, int offset) { + if (offset == 0) { + return; + } + final int pageRangeCount = pageRanges.length; + for (int i = 0; i < pageRangeCount; i++) { + final int start = pageRanges[i].getStart() + offset; + final int end = pageRanges[i].getEnd() + offset; + pageRanges[i] = new PageRange(start, end); + } + } + } + + private static final class AutoCancellingAnimator + implements OnAttachStateChangeListener, Runnable { + + private ViewPropertyAnimator mAnimator; + + private boolean mCancelled; + private Runnable mEndCallback; + + public static AutoCancellingAnimator animate(View view) { + ViewPropertyAnimator animator = view.animate(); + AutoCancellingAnimator cancellingWrapper = + new AutoCancellingAnimator(animator); + view.addOnAttachStateChangeListener(cancellingWrapper); + return cancellingWrapper; + } + + private AutoCancellingAnimator(ViewPropertyAnimator animator) { + mAnimator = animator; + } + + public AutoCancellingAnimator alpha(float alpha) { + mAnimator = mAnimator.alpha(alpha); + return this; + } + + public void cancel() { + mAnimator.cancel(); + } + + public AutoCancellingAnimator withLayer() { + mAnimator = mAnimator.withLayer(); + return this; + } + + public AutoCancellingAnimator withEndAction(Runnable callback) { + mEndCallback = callback; + mAnimator = mAnimator.withEndAction(this); + return this; + } + + public AutoCancellingAnimator scaleY(float scale) { + mAnimator = mAnimator.scaleY(scale); + return this; + } + + @Override + public void onViewAttachedToWindow(View v) { + /* do nothing */ + } + + @Override + public void onViewDetachedFromWindow(View v) { + cancel(); + } + + @Override + public void run() { + if (!mCancelled) { + mEndCallback.run(); + } + } + } + + private static final class PrintSpoolerProvider implements ServiceConnection { + private final Context mContext; + private final Runnable mCallback; + + private PrintSpoolerService mSpooler; + + public PrintSpoolerProvider(Context context, Runnable callback) { + mContext = context; + mCallback = callback; + Intent intent = new Intent(mContext, PrintSpoolerService.class); + mContext.bindService(intent, this, 0); + } + + public PrintSpoolerService getSpooler() { + return mSpooler; + } + + public void destroy() { + if (mSpooler != null) { + mContext.unbindService(this); + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService(); + if (mSpooler != null) { + mCallback.run(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + /* do noting - we are in the same process */ + } + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java new file mode 100644 index 0000000000000000000000000000000000000000..636e2456d3a8fc80b5edb03e933ee500b787cd8c --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -0,0 +1,1259 @@ +/* + * 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.printspooler; + +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.IBinder; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.IPrintSpooler; +import android.print.IPrintSpoolerCallbacks; +import android.print.IPrintSpoolerClient; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.print.PrintAttributes.Margins; +import android.print.PrintAttributes.MediaSize; +import android.print.PrintAttributes.Resolution; +import android.print.PrintDocumentInfo; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.os.HandlerCaller; +import com.android.internal.util.FastXmlSerializer; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Service for exposing some of the {@link PrintSpooler} functionality to + * another process. + */ +public final class PrintSpoolerService extends Service { + + private static final String LOG_TAG = "PrintSpoolerService"; + + private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false; + + private static final boolean DEBUG_PERSISTENCE = false; + + private static final boolean PERSISTNECE_MANAGER_ENABLED = true; + + private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000; + + private static final String PRINT_JOB_FILE_PREFIX = "print_job_"; + + private static final String PRINT_FILE_EXTENSION = "pdf"; + + private static final Object sLock = new Object(); + + private final Object mLock = new Object(); + + private final List mPrintJobs = new ArrayList(); + + private static PrintSpoolerService sInstance; + + private IPrintSpoolerClient mClient; + + private HandlerCaller mHandlerCaller; + + private PersistenceManager mPersistanceManager; + + private NotificationController mNotificationController; + + public static PrintSpoolerService peekInstance() { + synchronized (sLock) { + return sInstance; + } + } + + @Override + public void onCreate() { + super.onCreate(); + mHandlerCaller = new HandlerCaller(this, getMainLooper(), + new HandlerCallerCallback(), false); + + mPersistanceManager = new PersistenceManager(); + mNotificationController = new NotificationController(PrintSpoolerService.this); + + synchronized (mLock) { + mPersistanceManager.readStateLocked(); + handleReadPrintJobsLocked(); + } + + synchronized (sLock) { + sInstance = this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return new PrintSpooler(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (mLock) { + String prefix = (args.length > 0) ? args[0] : ""; + String tab = " "; + + pw.append(prefix).append("print jobs:").println(); + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + pw.append(prefix).append(tab).append(printJob.toString()); + pw.println(); + } + + pw.append(prefix).append("print job files:").println(); + File[] files = getFilesDir().listFiles(); + if (files != null) { + final int fileCount = files.length; + for (int i = 0; i < fileCount; i++) { + File file = files[i]; + if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) { + pw.append(prefix).append(tab).append(file.getName()).println(); + } + } + } + } + } + + private void sendOnPrintJobQueued(PrintJobInfo printJob) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob); + mHandlerCaller.executeOrSendMessage(message); + } + + private void sendOnAllPrintJobsForServiceHandled(ComponentName service) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service); + mHandlerCaller.executeOrSendMessage(message); + } + + private void sendOnAllPrintJobsHandled() { + Message message = mHandlerCaller.obtainMessage( + HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED); + mHandlerCaller.executeOrSendMessage(message); + } + + private final class HandlerCallerCallback implements HandlerCaller.Callback { + public static final int MSG_SET_CLIENT = 1; + public static final int MSG_ON_PRINT_JOB_QUEUED = 2; + public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3; + public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4; + public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5; + public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6; + + @Override + public void executeMessage(Message message) { + switch (message.what) { + case MSG_SET_CLIENT: { + synchronized (mLock) { + mClient = (IPrintSpoolerClient) message.obj; + if (mClient != null) { + Message msg = mHandlerCaller.obtainMessage( + HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED); + mHandlerCaller.sendMessageDelayed(msg, + CHECK_ALL_PRINTJOBS_HANDLED_DELAY); + } + } + } break; + + case MSG_ON_PRINT_JOB_QUEUED: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + if (mClient != null) { + try { + mClient.onPrintJobQueued(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error notify for a queued print job.", re); + } + } + } break; + + case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: { + ComponentName service = (ComponentName) message.obj; + if (mClient != null) { + try { + mClient.onAllPrintJobsForServiceHandled(service); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error notify for all print jobs per service" + + " handled.", re); + } + } + } break; + + case MSG_ON_ALL_PRINT_JOBS_HANDLED: { + if (mClient != null) { + try { + mClient.onAllPrintJobsHandled(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error notify for all print job handled.", re); + } + } + } break; + + case MSG_CHECK_ALL_PRINTJOBS_HANDLED: { + checkAllPrintJobsHandled(); + } break; + + case MSG_ON_PRINT_JOB_STATE_CHANGED: { + if (mClient != null) { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + try { + mClient.onPrintJobStateChanged(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error notify for print job state change.", re); + } + } + } break; + } + } + } + + public List getPrintJobInfos(ComponentName componentName, + int state, int appId) { + List foundPrintJobs = null; + synchronized (mLock) { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + PrinterId printerId = printJob.getPrinterId(); + final boolean sameComponent = (componentName == null + || (printerId != null + && componentName.equals(printerId.getServiceName()))); + final boolean sameAppId = appId == PrintManager.APP_ID_ANY + || printJob.getAppId() == appId; + final boolean sameState = (state == printJob.getState()) + || (state == PrintJobInfo.STATE_ANY) + || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS + && isStateVisibleToUser(printJob.getState())) + || (state == PrintJobInfo.STATE_ANY_ACTIVE + && isActiveState(printJob.getState())) + || (state == PrintJobInfo.STATE_ANY_SCHEDULED + && isScheduledState(printJob.getState())); + if (sameComponent && sameAppId && sameState) { + if (foundPrintJobs == null) { + foundPrintJobs = new ArrayList(); + } + foundPrintJobs.add(printJob); + } + } + } + return foundPrintJobs; + } + + private boolean isStateVisibleToUser(int state) { + return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED + || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED + || state == PrintJobInfo.STATE_BLOCKED)); + } + + public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) { + synchronized (mLock) { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (printJob.getId().equals(printJobId) + && (appId == PrintManager.APP_ID_ANY + || appId == printJob.getAppId())) { + return printJob; + } + } + return null; + } + } + + public void createPrintJob(PrintJobInfo printJob) { + synchronized (mLock) { + addPrintJobLocked(printJob); + setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null); + + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED, + printJob); + mHandlerCaller.executeOrSendMessage(message); + } + } + + private void handleReadPrintJobsLocked() { + // Make a map with the files for a print job since we may have + // to delete some. One example of getting orphan files if the + // spooler crashes while constructing a print job. We do not + // persist partially populated print jobs under construction to + // avoid special handling for various attributes missing. + ArrayMap fileForJobMap = null; + File[] files = getFilesDir().listFiles(); + if (files != null) { + final int fileCount = files.length; + for (int i = 0; i < fileCount; i++) { + File file = files[i]; + if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) { + if (fileForJobMap == null) { + fileForJobMap = new ArrayMap(); + } + String printJobIdString = file.getName().substring( + PRINT_JOB_FILE_PREFIX.length(), + file.getName().indexOf('.')); + PrintJobId printJobId = PrintJobId.unflattenFromString( + printJobIdString); + fileForJobMap.put(printJobId, file); + } + } + } + + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + + // We want to have only the orphan files at the end. + if (fileForJobMap != null) { + fileForJobMap.remove(printJob.getId()); + } + + switch (printJob.getState()) { + case PrintJobInfo.STATE_QUEUED: + case PrintJobInfo.STATE_STARTED: + case PrintJobInfo.STATE_BLOCKED: { + // We have a print job that was queued or started or blocked in + // the past but the device battery died or a crash occurred. In + // this case we assume the print job failed and let the user + // decide whether to restart the job or just cancel it. + setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED, + getString(R.string.no_connection_to_printer)); + } break; + } + } + + if (!mPrintJobs.isEmpty()) { + // Update the notification. + mNotificationController.onUpdateNotifications(mPrintJobs); + } + + // Delete the orphan files. + if (fileForJobMap != null) { + final int orphanFileCount = fileForJobMap.size(); + for (int i = 0; i < orphanFileCount; i++) { + File file = fileForJobMap.valueAt(i); + file.delete(); + } + } + } + + public void checkAllPrintJobsHandled() { + synchronized (mLock) { + if (!hasActivePrintJobsLocked()) { + notifyOnAllPrintJobsHandled(); + } + } + } + + public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) { + final PrintJobInfo printJob; + synchronized (mLock) { + printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + } + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + FileInputStream in = null; + FileOutputStream out = null; + try { + if (printJob != null) { + File file = generateFileForPrintJob(printJobId); + in = new FileInputStream(file); + out = new FileOutputStream(fd.getFileDescriptor()); + } + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + return null; + } + out.write(buffer, 0, readByteCount); + } + } catch (FileNotFoundException fnfe) { + Log.e(LOG_TAG, "Error writing print job data!", fnfe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing print job data!", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + IoUtils.closeQuietly(fd); + } + Log.i(LOG_TAG, "[END WRITE]"); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + public File generateFileForPrintJob(PrintJobId printJobId) { + return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX + + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION); + } + + private void addPrintJobLocked(PrintJobInfo printJob) { + mPrintJobs.add(printJob); + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[ADD] " + printJob); + } + } + + private void removeObsoletePrintJobs() { + synchronized (mLock) { + final int printJobCount = mPrintJobs.size(); + for (int i = printJobCount - 1; i >= 0; i--) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (isObsoleteState(printJob.getState())) { + mPrintJobs.remove(i); + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString()); + } + removePrintJobFileLocked(printJob.getId()); + } + } + mPersistanceManager.writeStateLocked(); + } + } + + private void removePrintJobFileLocked(PrintJobId printJobId) { + File file = generateFileForPrintJob(printJobId); + if (file.exists()) { + file.delete(); + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId); + } + } + } + + public boolean setPrintJobState(PrintJobId printJobId, int state, String error) { + boolean success = false; + + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + final int oldState = printJob.getState(); + if (oldState == state) { + return false; + } + + success = true; + + printJob.setState(state); + printJob.setStateReason(error); + printJob.setCancelling(false); + + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob); + } + + switch (state) { + case PrintJobInfo.STATE_COMPLETED: + case PrintJobInfo.STATE_CANCELED: + mPrintJobs.remove(printJob); + removePrintJobFileLocked(printJob.getId()); + // $fall-through$ + + case PrintJobInfo.STATE_FAILED: { + PrinterId printerId = printJob.getPrinterId(); + if (printerId != null) { + ComponentName service = printerId.getServiceName(); + if (!hasActivePrintJobsForServiceLocked(service)) { + sendOnAllPrintJobsForServiceHandled(service); + } + } + } break; + + case PrintJobInfo.STATE_QUEUED: { + sendOnPrintJobQueued(new PrintJobInfo(printJob)); + } break; + } + + if (shouldPersistPrintJob(printJob)) { + mPersistanceManager.writeStateLocked(); + } + + if (!hasActivePrintJobsLocked()) { + notifyOnAllPrintJobsHandled(); + } + + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED, + printJob); + mHandlerCaller.executeOrSendMessage(message); + + mNotificationController.onUpdateNotifications(mPrintJobs); + } + } + + return success; + } + + public boolean hasActivePrintJobsLocked() { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (isActiveState(printJob.getState())) { + return true; + } + } + return false; + } + + public boolean hasActivePrintJobsForServiceLocked(ComponentName service) { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (isActiveState(printJob.getState()) + && printJob.getPrinterId().getServiceName().equals(service)) { + return true; + } + } + return false; + } + + private boolean isObsoleteState(int printJobState) { + return (isTeminalState(printJobState) + || printJobState == PrintJobInfo.STATE_QUEUED); + } + + private boolean isScheduledState(int printJobState) { + return printJobState == PrintJobInfo.STATE_QUEUED + || printJobState == PrintJobInfo.STATE_STARTED + || printJobState == PrintJobInfo.STATE_BLOCKED; + } + + private boolean isActiveState(int printJobState) { + return printJobState == PrintJobInfo.STATE_CREATED + || printJobState == PrintJobInfo.STATE_QUEUED + || printJobState == PrintJobInfo.STATE_STARTED + || printJobState == PrintJobInfo.STATE_BLOCKED; + } + + private boolean isTeminalState(int printJobState) { + return printJobState == PrintJobInfo.STATE_COMPLETED + || printJobState == PrintJobInfo.STATE_CANCELED; + } + + public boolean setPrintJobTag(PrintJobId printJobId, String tag) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + String printJobTag = printJob.getTag(); + if (printJobTag == null) { + if (tag == null) { + return false; + } + } else if (printJobTag.equals(tag)) { + return false; + } + printJob.setTag(tag); + if (shouldPersistPrintJob(printJob)) { + mPersistanceManager.writeStateLocked(); + } + return true; + } + } + return false; + } + + public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setCancelling(cancelling); + if (shouldPersistPrintJob(printJob)) { + mPersistanceManager.writeStateLocked(); + } + mNotificationController.onUpdateNotifications(mPrintJobs); + + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED, + printJob); + mHandlerCaller.executeOrSendMessage(message); + } + } + } + + public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setCopies(copies); + } + } + } + + public void setPrintJobPrintDocumentInfoNoPersistence(PrintJobId printJobId, + PrintDocumentInfo info) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setDocumentInfo(info); + } + } + } + + public void setPrintJobAttributesNoPersistence(PrintJobId printJobId, + PrintAttributes attributes) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setAttributes(attributes); + } + } + } + + public void setPrintJobPrinterNoPersistence(PrintJobId printJobId, PrinterInfo printer) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setPrinterId(printer.getId()); + printJob.setPrinterName(printer.getName()); + } + } + } + + public void setPrintJobPagesNoPersistence(PrintJobId printJobId, PageRange[] pages) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setPages(pages); + } + } + } + + private boolean shouldPersistPrintJob(PrintJobInfo printJob) { + return printJob.getState() >= PrintJobInfo.STATE_QUEUED; + } + + private void notifyOnAllPrintJobsHandled() { + // This has to run on the tread that is persisting the current state + // since this call may result in the system unbinding from the spooler + // and as a result the spooler process may get killed before the write + // completes. + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + sendOnAllPrintJobsHandled(); + return null; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + private final class PersistenceManager { + private static final String PERSIST_FILE_NAME = "print_spooler_state.xml"; + + private static final String TAG_SPOOLER = "spooler"; + private static final String TAG_JOB = "job"; + + private static final String TAG_PRINTER_ID = "printerId"; + private static final String TAG_PAGE_RANGE = "pageRange"; + private static final String TAG_ATTRIBUTES = "attributes"; + private static final String TAG_DOCUMENT_INFO = "documentInfo"; + + private static final String ATTR_ID = "id"; + private static final String ATTR_LABEL = "label"; + private static final String ATTR_LABEL_RES_ID = "labelResId"; + private static final String ATTR_PACKAGE_NAME = "packageName"; + private static final String ATTR_STATE = "state"; + private static final String ATTR_APP_ID = "appId"; + private static final String ATTR_TAG = "tag"; + private static final String ATTR_CREATION_TIME = "creationTime"; + private static final String ATTR_COPIES = "copies"; + private static final String ATTR_PRINTER_NAME = "printerName"; + private static final String ATTR_STATE_REASON = "stateReason"; + private static final String ATTR_CANCELLING = "cancelling"; + + private static final String TAG_MEDIA_SIZE = "mediaSize"; + private static final String TAG_RESOLUTION = "resolution"; + private static final String TAG_MARGINS = "margins"; + + private static final String ATTR_COLOR_MODE = "colorMode"; + + private static final String ATTR_LOCAL_ID = "localId"; + private static final String ATTR_SERVICE_NAME = "serviceName"; + + private static final String ATTR_WIDTH_MILS = "widthMils"; + private static final String ATTR_HEIGHT_MILS = "heightMils"; + + private static final String ATTR_HORIZONTAL_DPI = "horizontalDip"; + private static final String ATTR_VERTICAL_DPI = "verticalDpi"; + + private static final String ATTR_LEFT_MILS = "leftMils"; + private static final String ATTR_TOP_MILS = "topMils"; + private static final String ATTR_RIGHT_MILS = "rightMils"; + private static final String ATTR_BOTTOM_MILS = "bottomMils"; + + private static final String ATTR_START = "start"; + private static final String ATTR_END = "end"; + + private static final String ATTR_NAME = "name"; + private static final String ATTR_PAGE_COUNT = "pageCount"; + private static final String ATTR_CONTENT_TYPE = "contentType"; + private static final String ATTR_DATA_SIZE = "dataSize"; + + private final AtomicFile mStatePersistFile; + + private boolean mWriteStateScheduled; + + private PersistenceManager() { + mStatePersistFile = new AtomicFile(new File(getFilesDir(), + PERSIST_FILE_NAME)); + } + + public void writeStateLocked() { + if (!PERSISTNECE_MANAGER_ENABLED) { + return; + } + if (mWriteStateScheduled) { + return; + } + mWriteStateScheduled = true; + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + synchronized (mLock) { + mWriteStateScheduled = false; + doWriteStateLocked(); + } + return null; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + private void doWriteStateLocked() { + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[PERSIST START]"); + } + FileOutputStream out = null; + try { + out = mStatePersistFile.startWrite(); + + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_SPOOLER); + + List printJobs = mPrintJobs; + + final int printJobCount = printJobs.size(); + for (int j = 0; j < printJobCount; j++) { + PrintJobInfo printJob = printJobs.get(j); + + serializer.startTag(null, TAG_JOB); + + serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString()); + serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString()); + serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState())); + serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId())); + String tag = printJob.getTag(); + if (tag != null) { + serializer.attribute(null, ATTR_TAG, tag); + } + serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf( + printJob.getCreationTime())); + serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies())); + String printerName = printJob.getPrinterName(); + if (!TextUtils.isEmpty(printerName)) { + serializer.attribute(null, ATTR_PRINTER_NAME, printerName); + } + String stateReason = printJob.getStateReason(); + if (!TextUtils.isEmpty(stateReason)) { + serializer.attribute(null, ATTR_STATE_REASON, stateReason); + } + serializer.attribute(null, ATTR_CANCELLING, String.valueOf( + printJob.isCancelling())); + + PrinterId printerId = printJob.getPrinterId(); + if (printerId != null) { + serializer.startTag(null, TAG_PRINTER_ID); + serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId()); + serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName() + .flattenToString()); + serializer.endTag(null, TAG_PRINTER_ID); + } + + PageRange[] pages = printJob.getPages(); + if (pages != null) { + for (int i = 0; i < pages.length; i++) { + serializer.startTag(null, TAG_PAGE_RANGE); + serializer.attribute(null, ATTR_START, String.valueOf( + pages[i].getStart())); + serializer.attribute(null, ATTR_END, String.valueOf( + pages[i].getEnd())); + serializer.endTag(null, TAG_PAGE_RANGE); + } + } + + PrintAttributes attributes = printJob.getAttributes(); + if (attributes != null) { + serializer.startTag(null, TAG_ATTRIBUTES); + + final int colorMode = attributes.getColorMode(); + serializer.attribute(null, ATTR_COLOR_MODE, + String.valueOf(colorMode)); + + MediaSize mediaSize = attributes.getMediaSize(); + if (mediaSize != null) { + serializer.startTag(null, TAG_MEDIA_SIZE); + serializer.attribute(null, ATTR_ID, mediaSize.getId()); + serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf( + mediaSize.getWidthMils())); + serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf( + mediaSize.getHeightMils())); + // We prefer to store only the package name and + // resource id and fallback to the label. + if (!TextUtils.isEmpty(mediaSize.mPackageName) + && mediaSize.mLabelResId > 0) { + serializer.attribute(null, ATTR_PACKAGE_NAME, + mediaSize.mPackageName); + serializer.attribute(null, ATTR_LABEL_RES_ID, + String.valueOf(mediaSize.mLabelResId)); + } else { + serializer.attribute(null, ATTR_LABEL, + mediaSize.getLabel(getPackageManager())); + } + serializer.endTag(null, TAG_MEDIA_SIZE); + } + + Resolution resolution = attributes.getResolution(); + if (resolution != null) { + serializer.startTag(null, TAG_RESOLUTION); + serializer.attribute(null, ATTR_ID, resolution.getId()); + serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf( + resolution.getHorizontalDpi())); + serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf( + resolution.getVerticalDpi())); + serializer.attribute(null, ATTR_LABEL, + resolution.getLabel()); + serializer.endTag(null, TAG_RESOLUTION); + } + + Margins margins = attributes.getMinMargins(); + if (margins != null) { + serializer.startTag(null, TAG_MARGINS); + serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf( + margins.getLeftMils())); + serializer.attribute(null, ATTR_TOP_MILS, String.valueOf( + margins.getTopMils())); + serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf( + margins.getRightMils())); + serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf( + margins.getBottomMils())); + serializer.endTag(null, TAG_MARGINS); + } + + serializer.endTag(null, TAG_ATTRIBUTES); + } + + PrintDocumentInfo documentInfo = printJob.getDocumentInfo(); + if (documentInfo != null) { + serializer.startTag(null, TAG_DOCUMENT_INFO); + serializer.attribute(null, ATTR_NAME, documentInfo.getName()); + serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf( + documentInfo.getContentType())); + serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf( + documentInfo.getPageCount())); + serializer.attribute(null, ATTR_DATA_SIZE, String.valueOf( + documentInfo.getDataSize())); + serializer.endTag(null, TAG_DOCUMENT_INFO); + } + + serializer.endTag(null, TAG_JOB); + + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[PERSISTED] " + printJob); + } + } + + serializer.endTag(null, TAG_SPOOLER); + serializer.endDocument(); + mStatePersistFile.finishWrite(out); + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[PERSIST END]"); + } + } catch (IOException e) { + Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e); + mStatePersistFile.failWrite(out); + } finally { + IoUtils.closeQuietly(out); + } + } + + public void readStateLocked() { + if (!PERSISTNECE_MANAGER_ENABLED) { + return; + } + FileInputStream in = null; + try { + in = mStatePersistFile.openRead(); + } catch (FileNotFoundException e) { + Log.i(LOG_TAG, "No existing print spooler state."); + return; + } + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseState(parser); + } catch (IllegalStateException ise) { + Slog.w(LOG_TAG, "Failed parsing ", ise); + } catch (NullPointerException npe) { + Slog.w(LOG_TAG, "Failed parsing ", npe); + } catch (NumberFormatException nfe) { + Slog.w(LOG_TAG, "Failed parsing ", nfe); + } catch (XmlPullParserException xppe) { + Slog.w(LOG_TAG, "Failed parsing ", xppe); + } catch (IOException ioe) { + Slog.w(LOG_TAG, "Failed parsing ", ioe); + } catch (IndexOutOfBoundsException iobe) { + Slog.w(LOG_TAG, "Failed parsing ", iobe); + } finally { + IoUtils.closeQuietly(in); + } + } + + private void parseState(XmlPullParser parser) + throws IOException, XmlPullParserException { + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER); + parser.next(); + + while (parsePrintJob(parser)) { + parser.next(); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER); + } + + private boolean parsePrintJob(XmlPullParser parser) + throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) { + return false; + } + + PrintJobInfo printJob = new PrintJobInfo(); + + PrintJobId printJobId = PrintJobId.unflattenFromString( + parser.getAttributeValue(null, ATTR_ID)); + printJob.setId(printJobId); + String label = parser.getAttributeValue(null, ATTR_LABEL); + printJob.setLabel(label); + final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE)); + printJob.setState(state); + final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID)); + printJob.setAppId(appId); + String tag = parser.getAttributeValue(null, ATTR_TAG); + printJob.setTag(tag); + String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME); + printJob.setCreationTime(Long.parseLong(creationTime)); + String copies = parser.getAttributeValue(null, ATTR_COPIES); + printJob.setCopies(Integer.parseInt(copies)); + String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME); + printJob.setPrinterName(printerName); + String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON); + printJob.setStateReason(stateReason); + String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING); + printJob.setCancelling(!TextUtils.isEmpty(cancelling) + ? Boolean.parseBoolean(cancelling) : false); + + parser.next(); + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) { + String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID); + ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue( + null, ATTR_SERVICE_NAME)); + printJob.setPrinterId(new PrinterId(service, localId)); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID); + parser.next(); + } + + skipEmptyTextTags(parser); + List pageRanges = null; + while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) { + final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START)); + final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END)); + PageRange pageRange = new PageRange(start, end); + if (pageRanges == null) { + pageRanges = new ArrayList(); + } + pageRanges.add(pageRange); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE); + parser.next(); + } + if (pageRanges != null) { + PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; + pageRanges.toArray(pageRangesArray); + printJob.setPages(pageRangesArray); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) { + + PrintAttributes.Builder builder = new PrintAttributes.Builder(); + + String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE); + builder.setColorMode(Integer.parseInt(colorMode)); + + parser.next(); + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) { + String id = parser.getAttributeValue(null, ATTR_ID); + label = parser.getAttributeValue(null, ATTR_LABEL); + final int widthMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_WIDTH_MILS)); + final int heightMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_HEIGHT_MILS)); + String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); + String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID); + final int labelResId = (labelResIdString != null) + ? Integer.parseInt(labelResIdString) : 0; + label = parser.getAttributeValue(null, ATTR_LABEL); + MediaSize mediaSize = new MediaSize(id, label, packageName, labelResId, + widthMils, heightMils); + builder.setMediaSize(mediaSize); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) { + String id = parser.getAttributeValue(null, ATTR_ID); + label = parser.getAttributeValue(null, ATTR_LABEL); + final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null, + ATTR_HORIZONTAL_DPI)); + final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null, + ATTR_VERTICAL_DPI)); + Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi); + builder.setResolution(resolution); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) { + final int leftMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_LEFT_MILS)); + final int topMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_TOP_MILS)); + final int rightMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_RIGHT_MILS)); + final int bottomMils = Integer.parseInt(parser.getAttributeValue(null, + ATTR_BOTTOM_MILS)); + Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils); + builder.setMinMargins(margins); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_MARGINS); + parser.next(); + } + + printJob.setAttributes(builder.build()); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES); + parser.next(); + } + + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) { + String name = parser.getAttributeValue(null, ATTR_NAME); + final int pageCount = Integer.parseInt(parser.getAttributeValue(null, + ATTR_PAGE_COUNT)); + final int contentType = Integer.parseInt(parser.getAttributeValue(null, + ATTR_CONTENT_TYPE)); + final int dataSize = Integer.parseInt(parser.getAttributeValue(null, + ATTR_DATA_SIZE)); + PrintDocumentInfo info = new PrintDocumentInfo.Builder(name) + .setPageCount(pageCount) + .setContentType(contentType).build(); + printJob.setDocumentInfo(info); + info.setDataSize(dataSize); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO); + parser.next(); + } + + mPrintJobs.add(printJob); + + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[RESTORED] " + printJob); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_JOB); + + return true; + } + + private void expect(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (!accept(parser, type, tag)) { + throw new XmlPullParserException("Exepected event: " + type + + " and tag: " + tag + " but got event: " + parser.getEventType() + + " and tag:" + parser.getName()); + } + } + + private void skipEmptyTextTags(XmlPullParser parser) + throws IOException, XmlPullParserException { + while (accept(parser, XmlPullParser.TEXT, null) + && "\n".equals(parser.getText())) { + parser.next(); + } + } + + private boolean accept(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (parser.getEventType() != type) { + return false; + } + if (tag != null) { + if (!tag.equals(parser.getName())) { + return false; + } + } else if (parser.getName() != null) { + return false; + } + return true; + } + } + + final class PrintSpooler extends IPrintSpooler.Stub { + @Override + public void getPrintJobInfos(IPrintSpoolerCallbacks callback, + ComponentName componentName, int state, int appId, int sequence) + throws RemoteException { + List printJobs = null; + try { + printJobs = PrintSpoolerService.this.getPrintJobInfos( + componentName, state, appId); + } finally { + callback.onGetPrintJobInfosResult(printJobs, sequence); + } + } + + @Override + public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback, + int appId, int sequence) throws RemoteException { + PrintJobInfo printJob = null; + try { + printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId); + } finally { + callback.onGetPrintJobInfoResult(printJob, sequence); + } + } + + @Override + public void createPrintJob(PrintJobInfo printJob) { + PrintSpoolerService.this.createPrintJob(printJob); + } + + @Override + public void setPrintJobState(PrintJobId printJobId, int state, String error, + IPrintSpoolerCallbacks callback, int sequece) throws RemoteException { + boolean success = false; + try { + success = PrintSpoolerService.this.setPrintJobState( + printJobId, state, error); + } finally { + callback.onSetPrintJobStateResult(success, sequece); + } + } + + @Override + public void setPrintJobTag(PrintJobId printJobId, String tag, + IPrintSpoolerCallbacks callback, int sequece) throws RemoteException { + boolean success = false; + try { + success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag); + } finally { + callback.onSetPrintJobTagResult(success, sequece); + } + } + + @Override + public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) { + PrintSpoolerService.this.writePrintJobData(fd, printJobId); + } + + @Override + public void setClient(IPrintSpoolerClient client) { + Message message = mHandlerCaller.obtainMessageO( + HandlerCallerCallback.MSG_SET_CLIENT, client); + mHandlerCaller.executeOrSendMessage(message); + } + + @Override + public void removeObsoletePrintJobs() { + PrintSpoolerService.this.removeObsoletePrintJobs(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + PrintSpoolerService.this.dump(fd, writer, args); + } + + @Override + public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) { + PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling); + } + + public PrintSpoolerService getService() { + return PrintSpoolerService.this; + } + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..fd14af929d1dbc8df8276a5e9fd0ed50f5b87e01 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java @@ -0,0 +1,140 @@ +/* + * 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.printspooler; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.ILayoutResultCallback; +import android.print.IPrintDocumentAdapter; +import android.print.IWriteResultCallback; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.util.Log; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * This class represents a remote print document adapter instance. + */ +final class RemotePrintDocumentAdapter { + private static final String LOG_TAG = "RemotePrintDocumentAdapter"; + + private static final boolean DEBUG = false; + + private final IPrintDocumentAdapter mRemoteInterface; + + private final File mFile; + + public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) { + mRemoteInterface = printAdatper; + mFile = file; + } + + public void start() { + if (DEBUG) { + Log.i(LOG_TAG, "start()"); + } + try { + mRemoteInterface.start(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling start()", re); + } + } + + public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + ILayoutResultCallback callback, Bundle metadata, int sequence) { + if (DEBUG) { + Log.i(LOG_TAG, "layout()"); + } + try { + mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling layout()", re); + } + } + + public void write(final PageRange[] pages, final IWriteResultCallback callback, + final int sequence) { + if (DEBUG) { + Log.i(LOG_TAG, "write()"); + } + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + InputStream in = null; + OutputStream out = null; + ParcelFileDescriptor source = null; + ParcelFileDescriptor sink = null; + try { + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + source = pipe[0]; + sink = pipe[1]; + + in = new FileInputStream(source.getFileDescriptor()); + out = new FileOutputStream(mFile); + + // Async call to initiate the other process writing the data. + mRemoteInterface.write(pages, sink, callback, sequence); + + // Close the source. It is now held by the client. + sink.close(); + sink = null; + + // Read the data. + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling write()", re); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error calling write()", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + IoUtils.closeQuietly(sink); + IoUtils.closeQuietly(source); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + public void finish() { + if (DEBUG) { + Log.i(LOG_TAG, "finish()"); + } + try { + mRemoteInterface.finish(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling finish()", re); + } + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..141dbd1e27bb3bc3660726045581c447ef1aabfb --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java @@ -0,0 +1,41 @@ +/* + * 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.printspooler; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.print.PrinterId; + +import com.android.printspooler.SelectPrinterFragment.OnPrinterSelectedListener; + +public class SelectPrinterActivity extends Activity implements OnPrinterSelectedListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.select_printer_activity); + } + + @Override + public void onPrinterSelected(PrinterId printer) { + Intent intent = new Intent(); + intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer); + setResult(RESULT_OK, intent); + finish(); + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..be94ba4c941841d6839f8e6303795f981f7c16a9 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java @@ -0,0 +1,562 @@ +/* + * 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.printspooler; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.app.ListFragment; +import android.app.LoaderManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Loader; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.print.PrintManager; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.printservice.PrintServiceInfo; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a fragment for selecting a printer. + */ +public final class SelectPrinterFragment extends ListFragment { + + private static final String LOG_TAG = "SelectPrinterFragment"; + + private static final int LOADER_ID_PRINTERS_LOADER = 1; + + private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG = + "FRAGMRNT_TAG_ADD_PRINTER_DIALOG"; + + private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS = + "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS"; + + private final ArrayList mAddPrinterServices = + new ArrayList(); + + private AnnounceFilterResult mAnnounceFilterResult; + + public static interface OnPrinterSelectedListener { + public void onPrinterSelected(PrinterId printerId); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + getActivity().getActionBar().setIcon(R.drawable.ic_menu_print); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final DestinationAdapter adapter = new DestinationAdapter(); + adapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + if (!getActivity().isFinishing() && adapter.getCount() <= 0) { + updateEmptyView(adapter); + } + } + + @Override + public void onInvalidated() { + if (!getActivity().isFinishing()) { + updateEmptyView(adapter); + } + } + }); + setListAdapter(adapter); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.select_printer_activity, menu); + + MenuItem searchItem = menu.findItem(R.id.action_search); + SearchView searchView = (SearchView) searchItem.getActionView(); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + return true; + } + + @Override + public boolean onQueryTextChange(String searchString) { + ((DestinationAdapter) getListAdapter()).getFilter().filter(searchString); + return true; + } + }); + searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + if (AccessibilityManager.getInstance(getActivity()).isEnabled()) { + view.announceForAccessibility(getString( + R.string.print_search_box_shown_utterance)); + } + } + @Override + public void onViewDetachedFromWindow(View view) { + Activity activity = getActivity(); + if (activity != null && !activity.isFinishing() + && AccessibilityManager.getInstance(activity).isEnabled()) { + view.announceForAccessibility(getString( + R.string.print_search_box_hidden_utterance)); + } + } + }); + + if (mAddPrinterServices.isEmpty()) { + menu.removeItem(R.id.action_add_printer); + } + } + + @Override + public void onResume() { + updateAddPrintersAdapter(); + getActivity().invalidateOptionsMenu(); + super.onResume(); + } + + @Override + public void onPause() { + if (mAnnounceFilterResult != null) { + mAnnounceFilterResult.remove(); + } + super.onPause(); + } + + @Override + public void onListItemClick(ListView list, View view, int position, long id) { + PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position); + Activity activity = getActivity(); + if (activity instanceof OnPrinterSelectedListener) { + ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId()); + } else { + throw new IllegalStateException("the host activity must implement" + + " OnPrinterSelectedListener"); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_add_printer) { + showAddPrinterSelectionDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void updateAddPrintersAdapter() { + mAddPrinterServices.clear(); + + // Get all enabled print services. + PrintManager printManager = (PrintManager) getActivity() + .getSystemService(Context.PRINT_SERVICE); + List enabledServices = printManager.getEnabledPrintServices(); + + // No enabled print services - done. + if (enabledServices.isEmpty()) { + return; + } + + // Find the services with valid add printers activities. + final int enabledServiceCount = enabledServices.size(); + for (int i = 0; i < enabledServiceCount; i++) { + PrintServiceInfo enabledService = enabledServices.get(i); + + // No add printers activity declared - done. + if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) { + continue; + } + + ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo; + ComponentName addPrintersComponentName = new ComponentName( + serviceInfo.packageName, enabledService.getAddPrintersActivityName()); + Intent addPritnersIntent = new Intent() + .setComponent(addPrintersComponentName); + + // The add printers activity is valid - add it. + PackageManager pm = getActivity().getPackageManager(); + List resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0); + if (!resolvedActivities.isEmpty()) { + // The activity is a component name, therefore it is one or none. + ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo; + if (activityInfo.exported + && (activityInfo.permission == null + || pm.checkPermission(activityInfo.permission, + getActivity().getPackageName()) + == PackageManager.PERMISSION_GRANTED)) { + mAddPrinterServices.add(enabledService); + } + } + } + } + + private void showAddPrinterSelectionDialog() { + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + Fragment oldFragment = getFragmentManager().findFragmentByTag( + FRAGMRNT_TAG_ADD_PRINTER_DIALOG); + if (oldFragment != null) { + transaction.remove(oldFragment); + } + AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS, + mAddPrinterServices); + newFragment.setArguments(arguments); + transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG); + transaction.commit(); + } + + public void updateEmptyView(DestinationAdapter adapter) { + if (getListView().getEmptyView() == null) { + View emptyView = getActivity().findViewById(R.id.empty_print_state); + getListView().setEmptyView(emptyView); + } + TextView titleView = (TextView) getActivity().findViewById(R.id.title); + View progressBar = getActivity().findViewById(R.id.progress_bar); + if (adapter.getUnfilteredCount() <= 0) { + titleView.setText(R.string.print_searching_for_printers); + progressBar.setVisibility(View.VISIBLE); + } else { + titleView.setText(R.string.print_no_printers); + progressBar.setVisibility(View.GONE); + } + } + + private void announceSearchResultIfNeeded() { + if (AccessibilityManager.getInstance(getActivity()).isEnabled()) { + if (mAnnounceFilterResult == null) { + mAnnounceFilterResult = new AnnounceFilterResult(); + } + mAnnounceFilterResult.post(); + } + } + + public static class AddPrinterAlertDialogFragment extends DialogFragment { + + private String mAddPrintServiceItem; + + @Override + @SuppressWarnings("unchecked") + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.choose_print_service); + + final List printServices = (List) (List) + getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS); + + final ArrayAdapter adapter = new ArrayAdapter( + getActivity(), android.R.layout.simple_list_item_1); + final int printServiceCount = printServices.size(); + for (int i = 0; i < printServiceCount; i++) { + PrintServiceInfo printService = printServices.get(i); + adapter.add(printService.getResolveInfo().loadLabel( + getActivity().getPackageManager()).toString()); + } + final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(), + Settings.Secure.PRINT_SERVICE_SEARCH_URI); + final Intent marketIntent; + if (!TextUtils.isEmpty(searchUri)) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); + if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) { + marketIntent = intent; + mAddPrintServiceItem = getString(R.string.add_print_service_label); + adapter.add(mAddPrintServiceItem); + } else { + marketIntent = null; + } + } else { + marketIntent = null; + } + + builder.setAdapter(adapter, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String item = adapter.getItem(which); + if (item == mAddPrintServiceItem) { + try { + startActivity(marketIntent); + } catch (ActivityNotFoundException anfe) { + Log.w(LOG_TAG, "Couldn't start add printer activity", anfe); + } + } else { + PrintServiceInfo printService = printServices.get(which); + ComponentName componentName = new ComponentName( + printService.getResolveInfo().serviceInfo.packageName, + printService.getAddPrintersActivityName()); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(componentName); + try { + startActivity(intent); + } catch (ActivityNotFoundException anfe) { + Log.w(LOG_TAG, "Couldn't start settings activity", anfe); + } + } + } + }); + + return builder.create(); + } + } + + private final class DestinationAdapter extends BaseAdapter + implements LoaderManager.LoaderCallbacks>, Filterable { + + private final Object mLock = new Object(); + + private final List mPrinters = new ArrayList(); + + private final List mFilteredPrinters = new ArrayList(); + + private CharSequence mLastSearchString; + + public DestinationAdapter() { + getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this); + } + + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + synchronized (mLock) { + if (TextUtils.isEmpty(constraint)) { + return null; + } + FilterResults results = new FilterResults(); + List filteredPrinters = new ArrayList(); + String constraintLowerCase = constraint.toString().toLowerCase(); + final int printerCount = mPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo printer = mPrinters.get(i); + if (printer.getName().toLowerCase().contains(constraintLowerCase)) { + filteredPrinters.add(printer); + } + } + results.values = filteredPrinters; + results.count = filteredPrinters.size(); + return results; + } + } + + @Override + @SuppressWarnings("unchecked") + protected void publishResults(CharSequence constraint, FilterResults results) { + final boolean resultCountChanged; + synchronized (mLock) { + final int oldPrinterCount = mFilteredPrinters.size(); + mLastSearchString = constraint; + mFilteredPrinters.clear(); + if (results == null) { + mFilteredPrinters.addAll(mPrinters); + } else { + List printers = (List) results.values; + mFilteredPrinters.addAll(printers); + } + resultCountChanged = (oldPrinterCount != mFilteredPrinters.size()); + } + if (resultCountChanged) { + announceSearchResultIfNeeded(); + } + notifyDataSetChanged(); + } + }; + } + + public int getUnfilteredCount() { + synchronized (mLock) { + return mPrinters.size(); + } + } + + @Override + public int getCount() { + synchronized (mLock) { + return mFilteredPrinters.size(); + } + } + + @Override + public Object getItem(int position) { + synchronized (mLock) { + return mFilteredPrinters.get(position); + } + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + return getView(position, convertView, parent); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = getActivity().getLayoutInflater().inflate( + R.layout.printer_dropdown_item, parent, false); + } + + convertView.setEnabled(isEnabled(position)); + + CharSequence title = null; + CharSequence subtitle = null; + Drawable icon = null; + + PrinterInfo printer = (PrinterInfo) getItem(position); + title = printer.getName(); + try { + PackageManager pm = getActivity().getPackageManager(); + PackageInfo packageInfo = pm.getPackageInfo(printer.getId() + .getServiceName().getPackageName(), 0); + subtitle = packageInfo.applicationInfo.loadLabel(pm); + icon = packageInfo.applicationInfo.loadIcon(pm); + } catch (NameNotFoundException nnfe) { + /* ignore */ + } + + TextView titleView = (TextView) convertView.findViewById(R.id.title); + titleView.setText(title); + + TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); + if (!TextUtils.isEmpty(subtitle)) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(View.VISIBLE); + } else { + subtitleView.setText(null); + subtitleView.setVisibility(View.GONE); + } + + + ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); + if (icon != null) { + iconView.setImageDrawable(icon); + iconView.setVisibility(View.VISIBLE); + } else { + iconView.setVisibility(View.GONE); + } + + return convertView; + } + + @Override + public boolean isEnabled(int position) { + PrinterInfo printer = (PrinterInfo) getItem(position); + return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + if (id == LOADER_ID_PRINTERS_LOADER) { + return new FusedPrintersProvider(getActivity()); + } + return null; + } + + @Override + public void onLoadFinished(Loader> loader, + List printers) { + synchronized (mLock) { + mPrinters.clear(); + mPrinters.addAll(printers); + mFilteredPrinters.clear(); + mFilteredPrinters.addAll(printers); + if (!TextUtils.isEmpty(mLastSearchString)) { + getFilter().filter(mLastSearchString); + } + } + notifyDataSetChanged(); + } + + @Override + public void onLoaderReset(Loader> loader) { + synchronized (mLock) { + mPrinters.clear(); + mFilteredPrinters.clear(); + } + notifyDataSetInvalidated(); + } + } + + private final class AnnounceFilterResult implements Runnable { + private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec + + public void post() { + remove(); + getListView().postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY); + } + + public void remove() { + getListView().removeCallbacks(this); + } + + @Override + public void run() { + final int count = getListView().getAdapter().getCount(); + final String text; + if (count <= 0) { + text = getString(R.string.print_no_printers); + } else { + text = getActivity().getResources().getQuantityString( + R.plurals.print_search_result_count_utterance, count, count); + } + getListView().announceForAccessibility(text); + } + } +} diff --git a/packages/SettingsProvider/Android.mk b/packages/SettingsProvider/Android.mk index a2ea55465239f3e1051447550669b5375652065d..da929ae6f1a08758b00536500cc2da3889d28f94 100644 --- a/packages/SettingsProvider/Android.mk +++ b/packages/SettingsProvider/Android.mk @@ -9,6 +9,7 @@ LOCAL_JAVA_LIBRARIES := telephony-common LOCAL_PACKAGE_NAME := SettingsProvider LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true include $(BUILD_PACKAGE) diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index ab2feb9b2c3c4efc20da9b367753499741eb1528..783aa0342ae4b97c7d7d4a2828241063b2a65650 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -8,7 +8,7 @@ android:process="system" android:backupAgent="SettingsBackupAgent" android:killAfterRestore="false" - android:icon="@drawable/ic_launcher_settings"> + android:icon="@mipmap/ic_launcher_settings"> diff --git a/packages/SettingsProvider/res/drawable-hdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/drawable-hdpi/ic_launcher_settings.png deleted file mode 100644 index 8a5a2f704bb50dbd2c463cc2d0286dd66e0568eb..0000000000000000000000000000000000000000 Binary files a/packages/SettingsProvider/res/drawable-hdpi/ic_launcher_settings.png and /dev/null differ diff --git a/packages/SettingsProvider/res/drawable-mdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/drawable-mdpi/ic_launcher_settings.png deleted file mode 100644 index 803439ff4ae07860e98e6adee5b52af33834db62..0000000000000000000000000000000000000000 Binary files a/packages/SettingsProvider/res/drawable-mdpi/ic_launcher_settings.png and /dev/null differ diff --git a/packages/SettingsProvider/res/drawable-xhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/drawable-xhdpi/ic_launcher_settings.png deleted file mode 100644 index ec3c8ea3e14eee04c349692678e5dd8c668ba5f5..0000000000000000000000000000000000000000 Binary files a/packages/SettingsProvider/res/drawable-xhdpi/ic_launcher_settings.png and /dev/null differ diff --git a/packages/SettingsProvider/res/mipmap-hdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-hdpi/ic_launcher_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..a8ccc8938124859d55dca58175923f2dfa17cb0b Binary files /dev/null and b/packages/SettingsProvider/res/mipmap-hdpi/ic_launcher_settings.png differ diff --git a/packages/SettingsProvider/res/mipmap-mdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-mdpi/ic_launcher_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..69709a8b4ed59bda85d0fb67489db7ee286f71a7 Binary files /dev/null and b/packages/SettingsProvider/res/mipmap-mdpi/ic_launcher_settings.png differ diff --git a/packages/SettingsProvider/res/mipmap-xhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-xhdpi/ic_launcher_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..c3adce611a15780b1e90049fb001a032caef5f31 Binary files /dev/null and b/packages/SettingsProvider/res/mipmap-xhdpi/ic_launcher_settings.png differ diff --git a/packages/SettingsProvider/res/mipmap-xxhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-xxhdpi/ic_launcher_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..52fe9785598a83145756c9bbc53f152bdfdc5459 Binary files /dev/null and b/packages/SettingsProvider/res/mipmap-xxhdpi/ic_launcher_settings.png differ diff --git a/packages/SettingsProvider/res/mipmap-xxxhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-xxxhdpi/ic_launcher_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..6b92795d421f445e393992755f98fa0258b7d21d Binary files /dev/null and b/packages/SettingsProvider/res/mipmap-xxxhdpi/ic_launcher_settings.png differ diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index bfb0931c5dae938023295e129b1de99b94dc9694..a1d8f22d48a96d88ca47cb008d62a940aa651ddc 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -168,4 +168,9 @@ false + + + 0 diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 120b28f2fcbd786f574d745576fc6412d36ccd86..09c21f3f199760042195e12c42aaf1b0a725915e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -72,7 +72,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion' // is properly propagated through your change. Not doing so will result in a loss of user // settings. - private static final int DATABASE_VERSION = 97; + private static final int DATABASE_VERSION = 98; private Context mContext; private int mUserHandle; @@ -1541,6 +1541,24 @@ public class DatabaseHelper extends SQLiteOpenHelper { upgradeVersion = 97; } + if (upgradeVersion == 97) { + if (mUserHandle == UserHandle.USER_OWNER) { + db.beginTransaction(); + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement("INSERT OR REPLACE INTO global(name,value)" + + " VALUES(?,?);"); + loadIntegerSetting(stmt, Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, + R.integer.def_low_battery_sound_timeout); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + if (stmt != null) stmt.close(); + } + } + upgradeVersion = 98; + } + // *** Remember to update DATABASE_VERSION above! if (upgradeVersion != currentVersion) { @@ -2253,6 +2271,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { CdmaSubscriptionSourceManager.PREFERRED_CDMA_SUBSCRIPTION); loadSetting(stmt, Settings.Global.CDMA_SUBSCRIPTION_MODE, type); + loadIntegerSetting(stmt, Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, + R.integer.def_low_battery_sound_timeout); + // --- New global settings start here } finally { if (stmt != null) stmt.close(); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 3f044708d5a0d1b3b3f63b3500e3aa81d563a98b..7b09092508ad09491cb832d431bf2080d89cef3d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -137,6 +137,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { static class Network { String ssid = ""; // equals() and hashCode() need these to be non-null String key_mgmt = ""; + boolean certUsed = false; final ArrayList rawLines = new ArrayList(); public static Network readFromStream(BufferedReader in) { @@ -167,6 +168,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { ssid = line; } else if (line.startsWith("key_mgmt")) { key_mgmt = line; + } else if (line.startsWith("client_cert=")) { + certUsed = true; + } else if (line.startsWith("ca_cert=")) { + certUsed = true; + } else if (line.startsWith("ca_path=")) { + certUsed = true; } } @@ -246,6 +253,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { public void write(Writer w) throws IOException { for (Network net : mNetworks) { + if (net.certUsed) { + // Networks that use certificates for authentication can't be restored + // because the certificates they need don't get restored (because they + // are stored in keystore, and can't be restored) + continue; + } + net.write(w); } } @@ -738,10 +752,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + // Intercept the keys and see if they need special handling + value = mSettingsHelper.onBackupValue(key, value); + if (value == null) { continue; } - // Write the key and value in the intermediary array. byte[] keyBytes = key.getBytes(); totalSize += INTEGER_BYTE_COUNT + keyBytes.length; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index a446e40f925dd4e883977a515415e1aa0e5226a2..dd7a828626f4eda278fa90466fe4eef6c93b94bb 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.res.Configuration; import android.location.LocationManager; import android.media.AudioManager; +import android.media.RingtoneManager; +import android.net.Uri; import android.os.IPowerManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -33,6 +35,7 @@ import android.text.TextUtils; import java.util.Locale; public class SettingsHelper { + private static final String SILENT_RINGTONE = "_silent"; private Context mContext; private AudioManager mAudioManager; @@ -63,10 +66,60 @@ public class SettingsHelper { setAutoRestore(Integer.parseInt(value) == 1); } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) { return false; + } else if (Settings.System.RINGTONE.equals(name) + || Settings.System.NOTIFICATION_SOUND.equals(name)) { + setRingtone(name, value); + return false; } return true; } + public String onBackupValue(String name, String value) { + // Special processing for backing up ringtones + if (Settings.System.RINGTONE.equals(name) + || Settings.System.NOTIFICATION_SOUND.equals(name)) { + if (value == null) { + // Silent ringtone + return SILENT_RINGTONE; + } else { + return getCanonicalRingtoneValue(value); + } + } + // Return the original value + return value; + } + + /** + * Sets the ringtone of type specified by the name. + * + * @param name should be Settings.System.RINGTONE or Settings.System.NOTIFICATION_SOUND. + * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone. + */ + private void setRingtone(String name, String value) { + // If it's null, don't change the default + if (value == null) return; + Uri ringtoneUri = null; + if (SILENT_RINGTONE.equals(value)) { + ringtoneUri = null; + } else { + Uri canonicalUri = Uri.parse(value); + ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri); + if (ringtoneUri == null) { + // Unrecognized or invalid Uri, don't restore + return; + } + } + final int ringtoneType = Settings.System.RINGTONE.equals(name) + ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION; + RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri); + } + + private String getCanonicalRingtoneValue(String value) { + final Uri ringtoneUri = Uri.parse(value); + final Uri canonicalUri = mContext.getContentResolver().canonicalize(ringtoneUri); + return canonicalUri == null ? null : canonicalUri.toString(); + } + private boolean isAlreadyConfiguredCriticalAccessibilitySetting(String name) { // These are the critical accessibility settings that are required for a // blind user to be able to interact with the device. If these settings are diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 017750443736813a2ba670650e36566284f2a0d5..158227fe78af23b6e258ae4b9539ad136fd9ad36 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.AssetFileDescriptor; import android.database.AbstractCursor; import android.database.Cursor; @@ -46,10 +47,10 @@ import android.os.Bundle; import android.os.DropBoxManager; import android.os.FileObserver; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; -import android.provider.DrmStore; import android.provider.MediaStore; import android.provider.Settings; import android.text.TextUtils; @@ -62,6 +63,8 @@ public class SettingsProvider extends ContentProvider { private static final String TAG = "SettingsProvider"; private static final boolean LOCAL_LOGV = false; + private static final boolean USER_CHECK_THROWS = true; + private static final String TABLE_SYSTEM = "system"; private static final String TABLE_SECURE = "secure"; private static final String TABLE_GLOBAL = "global"; @@ -478,6 +481,13 @@ public class SettingsProvider extends ContentProvider { try { final String value = c.moveToNext() ? c.getString(0) : null; if (value == null) { + // sanity-check the user before touching the db + final UserInfo user = mUserManager.getUserInfo(userHandle); + if (user == null) { + // can happen due to races when deleting users; treat as benign + return false; + } + final SecureRandom random = new SecureRandom(); final String newAndroidIdValue = Long.toHexString(random.nextLong()); final ContentValues values = new ContentValues(); @@ -491,7 +501,7 @@ public class SettingsProvider extends ContentProvider { Slog.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue + "] for user " + userHandle); // Write a dropbox entry if it's a restricted profile - if (mUserManager.getUserInfo(userHandle).isRestricted()) { + if (user.isRestricted()) { DropBoxManager dbm = (DropBoxManager) getContext().getSystemService(Context.DROPBOX_SERVICE); if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) { @@ -515,6 +525,14 @@ public class SettingsProvider extends ContentProvider { // Lazy initialize the database helper and caches for this user, if necessary private DatabaseHelper getOrEstablishDatabase(int callingUser) { + if (callingUser >= Process.SYSTEM_UID) { + if (USER_CHECK_THROWS) { + throw new IllegalArgumentException("Uid rather than user handle: " + callingUser); + } else { + Slog.wtf(TAG, "establish db for uid rather than user: " + callingUser); + } + } + long oldId = Binder.clearCallingIdentity(); try { DatabaseHelper dbHelper = mOpenHelpers.get(callingUser); @@ -560,8 +578,7 @@ public class SettingsProvider extends ContentProvider { * Fast path that avoids the use of chatty remoted Cursors. */ @Override - public Bundle callFromPackage(String callingPackage, String method, String request, - Bundle args) { + public Bundle call(String method, String request, Bundle args) { int callingUser = UserHandle.getCallingUserId(); if (args != null) { int reqUser = args.getInt(Settings.CALL_METHOD_USER_KEY, callingUser); @@ -616,7 +633,7 @@ public class SettingsProvider extends ContentProvider { // Also need to take care of app op. if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SETTINGS, Binder.getCallingUid(), - callingPackage) != AppOpsManager.MODE_ALLOWED) { + getCallingPackage()) != AppOpsManager.MODE_ALLOWED) { return null; } @@ -992,7 +1009,7 @@ public class SettingsProvider extends ContentProvider { /* * When a client attempts to openFile the default ringtone or * notification setting Uri, we will proxy the call to the current - * default ringtone's Uri (if it is in the DRM or media provider). + * default ringtone's Uri (if it is in the media provider). */ int ringtoneType = RingtoneManager.getDefaultType(uri); // Above call returns -1 if the Uri doesn't match a default type @@ -1003,22 +1020,9 @@ public class SettingsProvider extends ContentProvider { Uri soundUri = RingtoneManager.getActualDefaultRingtoneUri(context, ringtoneType); if (soundUri != null) { - // Only proxy the openFile call to drm or media providers + // Proxy the openFile call to media provider String authority = soundUri.getAuthority(); - boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY); - if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) { - - if (isDrmAuthority) { - try { - // Check DRM access permission here, since once we - // do the below call the DRM will be checking our - // permission, not our caller's permission - DrmStore.enforceAccessDrmPermission(context); - } catch (SecurityException e) { - throw new FileNotFoundException(e.getMessage()); - } - } - + if (authority.equals(MediaStore.AUTHORITY)) { return context.getContentResolver().openFileDescriptor(soundUri, mode); } } @@ -1033,7 +1037,7 @@ public class SettingsProvider extends ContentProvider { /* * When a client attempts to openFile the default ringtone or * notification setting Uri, we will proxy the call to the current - * default ringtone's Uri (if it is in the DRM or media provider). + * default ringtone's Uri (if it is in the media provider). */ int ringtoneType = RingtoneManager.getDefaultType(uri); // Above call returns -1 if the Uri doesn't match a default type @@ -1044,22 +1048,9 @@ public class SettingsProvider extends ContentProvider { Uri soundUri = RingtoneManager.getActualDefaultRingtoneUri(context, ringtoneType); if (soundUri != null) { - // Only proxy the openFile call to drm or media providers + // Proxy the openFile call to media provider String authority = soundUri.getAuthority(); - boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY); - if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) { - - if (isDrmAuthority) { - try { - // Check DRM access permission here, since once we - // do the below call the DRM will be checking our - // permission, not our caller's permission - DrmStore.enforceAccessDrmPermission(context); - } catch (SecurityException e) { - throw new FileNotFoundException(e.getMessage()); - } - } - + if (authority.equals(MediaStore.AUTHORITY)) { ParcelFileDescriptor pfd = null; try { pfd = context.getContentResolver().openFileDescriptor(soundUri, mode); diff --git a/packages/SharedStorageBackup/Android.mk b/packages/SharedStorageBackup/Android.mk index 1d4f4da70d0d064bb36fd29505ac6c455d3b4bf9..a213965f085b4e38ed0e7d6a6cc292c9b063a010 100644 --- a/packages/SharedStorageBackup/Android.mk +++ b/packages/SharedStorageBackup/Android.mk @@ -25,6 +25,7 @@ LOCAL_PROGUARD_FLAG_FILES := proguard.flags LOCAL_PACKAGE_NAME := SharedStorageBackup LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true include $(BUILD_PACKAGE) diff --git a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java index 7ebe096e3f495e0eba2c780e75c13f3c68de166e..0485334fd904981ebe22975e3b068de057e8e1da 100644 --- a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java +++ b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java @@ -57,7 +57,7 @@ public class ObbBackupService extends Service { int token, IBackupManager callbackBinder) { final FileDescriptor outFd = data.getFileDescriptor(); try { - File obbDir = Environment.getExternalStorageAppObbDirectory(packageName); + File obbDir = Environment.buildExternalStorageAppObbDirs(packageName)[0]; if (obbDir != null) { if (obbDir.exists()) { ArrayList obbList = allFileContents(obbDir); @@ -106,7 +106,7 @@ public class ObbBackupService extends Service { long fileSize, int type, String path, long mode, long mtime, int token, IBackupManager callbackBinder) { try { - File outFile = Environment.getExternalStorageAppObbDirectory(packageName); + File outFile = Environment.buildExternalStorageAppObbDirs(packageName)[0]; if (outFile != null) { outFile = new File(outFile, path); } diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk index fc4c0f57475d06bfae95dac0d29fc4c849432e3e..5bd48c63433cd0b8c6c0a7a919811d378396ebf5 100644 --- a/packages/Shell/Android.mk +++ b/packages/Shell/Android.mk @@ -9,5 +9,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 LOCAL_PACKAGE_NAME := Shell LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true include $(BUILD_PACKAGE) diff --git a/packages/Shell/res/values-af/strings.xml b/packages/Shell/res/values-af/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..3dc6a0fe380dff5f97da268ba0b98a9b64458658 --- /dev/null +++ b/packages/Shell/res/values-af/strings.xml @@ -0,0 +1,24 @@ + + + + + "Tuisskerm" + "Foutverslag vasgevang" + "Raak om jou foutverslag te deel" + "Foutverslae bevat data van die stelsel se verskillende loglêers af, insluitend persoonlike en private inligting. Deel foutverslae net met programme en mense wat jy vertrou." + "Wys hierdie boodskap volgende keer" + diff --git a/packages/Shell/res/values-am/strings.xml b/packages/Shell/res/values-am/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..c90a5f519908cc21c561d76efb7b4195fa9f4695 --- /dev/null +++ b/packages/Shell/res/values-am/strings.xml @@ -0,0 +1,24 @@ + + + + + "ቀáŽ" + "የሳንካ ሪá–ርት ተይዟáˆ" + "የሳንካ ሪá–ርትዎን ለማጋራት ይንክኩ" + "የሳንካ ሪá–ርቶች የáŒáˆ መረጃን ጨáˆáˆ® ከበርካታ የስርዓቱ áˆá‹áŒá‰¥ ማስታወሻዎች የመጣ á‹áˆ‚ብን á‹­á‹Ÿáˆá¢ የሳንካ ሪá–ርቶች ለሚያáˆáŠ—ቸዠመተáŒá‰ áˆªá‹«á‹Žá‰½áŠ• እና ሰዎችን ብቻ ያጋሩá¢" + "ይህን መáˆá‹•áŠ­á‰µ በሚቀጥለዠጊዜ አሳይ" + diff --git a/packages/Shell/res/values-ar/strings.xml b/packages/Shell/res/values-ar/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..6a595d5e2ed687d867c5362dc915ad25f7e7e36b --- /dev/null +++ b/packages/Shell/res/values-ar/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "تم الحصول على تقرير الأخطاء" + "المس لمشاركة تقرير الأخطاء" + "تحتوي تقارير الأخطاء على بيانات من ملÙات سجلات النظام المتنوعة، بما ÙÙŠ ذلك معلومات شخصية وخاصة. لا تشارك تقارير الأخطاء إلا مع التطبيقات والأشخاص الموثوق بهم." + "إظهار هذه الرسالة ÙÙŠ المرة القادمة" + diff --git a/packages/Shell/res/values-be/strings.xml b/packages/Shell/res/values-be/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..e713975a4b0d801f73fec43b181954a2aada2ad8 --- /dev/null +++ b/packages/Shell/res/values-be/strings.xml @@ -0,0 +1,24 @@ + + + + + "Ðбалонка" + "Справаздача пра збой захавана" + "ÐаціÑніце, каб падзÑліцца Ñваёй Ñправаздачай пра збой" + "Справаздача пра памылку ўтрымлівае Ð´Ð°Ð´Ð·ÐµÐ½Ñ‹Ñ Ð· гiÑторыi ÑÑ–ÑÑ‚Ñмных файлаў, у тым ліку перÑанальную Ñ– прыватную інфармацыю. ДзÑлiцеÑÑ Ñправаздачамi пра збоi толькi з праверанымi карыÑтальнiкамi i прыкладаннÑмi." + "У наÑтупны раз паказваць гÑта паведамленне" + diff --git a/packages/Shell/res/values-bg/strings.xml b/packages/Shell/res/values-bg/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..2fae9531071244ac04130f03f2433502a6f88f27 --- /dev/null +++ b/packages/Shell/res/values-bg/strings.xml @@ -0,0 +1,24 @@ + + + + + "Команден ред" + "Отчетът за програмни грешки е запиÑан" + "ДокоÑнете, за да Ñподелите отчета Ñи за програмни грешки" + "Отчетите за програмни грешки Ñъдържат данни от различни региÑтрационни файлове на ÑиÑтемата, включително лична и поверителна информациÑ. СподелÑйте ги Ñамо Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸ хора, на които имате доверие." + "Това Ñъобщение да Ñе показва ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð¿ÑŠÑ‚" + diff --git a/packages/Shell/res/values-ca/strings.xml b/packages/Shell/res/values-ca/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..8bf368a051ae98c1530ec742b2d765a66c6a8543 --- /dev/null +++ b/packages/Shell/res/values-ca/strings.xml @@ -0,0 +1,24 @@ + + + + + "Protecció" + "S\'ha registrat l\'informe d\'error" + "Toca aquí per compartir el teu informe d\'error." + "Els informes d\'error contenen dades dels diferents fitxers de registre del sistema, inclosa informació privada i personal. Comparteix els informes d\'error només amb les aplicacions i amb les persones en qui confies." + "Mostra aquest missatge la propera vegada" + diff --git a/packages/Shell/res/values-cs/strings.xml b/packages/Shell/res/values-cs/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..effdcb97f24ab04fd00cb3608e17b3395b14a5c7 --- /dev/null +++ b/packages/Shell/res/values-cs/strings.xml @@ -0,0 +1,24 @@ + + + + + "ProstÅ™edí" + "Byla vytvoÅ™ena zpráva o chybÄ›" + "Zprávu o chybÄ› můžete sdílet klepnutím." + "Zprávy o chybách obsahují data z různých souborů protokolů systému vÄetnÄ› osobních a soukromých informací. Zprávy o chybách sdílejte pouze s aplikacemi a uživateli, kterým důvěřujete." + "Zobrazit tuto zprávu příštÄ›" + diff --git a/packages/Shell/res/values-da/strings.xml b/packages/Shell/res/values-da/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..01ea42b68687121ce3ef31710e2eee8dd6aec0a3 --- /dev/null +++ b/packages/Shell/res/values-da/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Fejlrapporten er registreret" + "Tryk for at dele din fejlrapport" + "Fejlrapporter indeholder data fra systemets forskellige logfiler, herunder personlige og private oplysninger. Del kun fejlrapporter med apps og personer, du har tillid til." + "Vis denne meddelelse næste gang" + diff --git a/packages/Shell/res/values-de/strings.xml b/packages/Shell/res/values-de/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..99522b1e8d107cff6df70941090bc9fd483d7c67 --- /dev/null +++ b/packages/Shell/res/values-de/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Fehlerbericht erfasst" + "Berühren, um Fehlerbericht zu teilen" + "Fehlerberichte enthalten Daten aus verschiedenen Protokolldateien des Systems, darunter auch personenbezogene und private Daten. Teilen Sie Fehlerberichte nur mit Apps und Personen, denen Sie vertrauen." + "Diese Nachricht nächstes Mal zeigen" + diff --git a/packages/Shell/res/values-el/strings.xml b/packages/Shell/res/values-el/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..3669f789c98248091bb1f28e895e5b0d1808b84f --- /dev/null +++ b/packages/Shell/res/values-el/strings.xml @@ -0,0 +1,24 @@ + + + + + "Κέλυφος" + "Η λήψη της αναφοÏάς ήταν επιτυχής" + "Αγγίξτε για κοινή χÏήση της αναφοÏάς σας σφαλμάτων" + "Οι αναφοÏές σφαλμάτων πεÏιέχουν δεδομένα από τα διάφοÏα αÏχεία καταγÏαφής του συστήματος, συμπεÏιλαμβανομένων Ï€Ïοσωπικών και ιδιωτικών πληÏοφοÏιών. Îα μοιÏάζεστε αναφοÏές σφαλμάτων μόνο με εφαÏμογές και άτομα που εμπιστεÏεστε." + "Εμφάνιση Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… μηνÏματος την επόμενη φοÏά" + diff --git a/packages/Shell/res/values-en-rGB/strings.xml b/packages/Shell/res/values-en-rGB/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..68708e0c576fa735bfb776f7917efc9bb01c7124 --- /dev/null +++ b/packages/Shell/res/values-en-rGB/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Bug report captured" + "Touch to share your bug report" + "Bug reports contain data from the system\'s various log files, including personal and private information. Only share bug reports with apps and people that you trust." + "Show this message next time" + diff --git a/packages/Shell/res/values-es-rUS/strings.xml b/packages/Shell/res/values-es-rUS/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..f1ec75c1366e2912952c7484362e75bf2c61149b --- /dev/null +++ b/packages/Shell/res/values-es-rUS/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Informe de errores capturado" + "Toca para compartir tu informe de errores." + "Los informes de errores contienen datos de los distintos archivos de registro del sistema, incluida la información personal y privada. Comparte los informes de errores únicamente con aplicaciones y personas en las que confíes." + "Mostrar este mensaje la próxima vez" + diff --git a/packages/Shell/res/values-es/strings.xml b/packages/Shell/res/values-es/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..7990672b97ce611a12632312089637459da24e03 --- /dev/null +++ b/packages/Shell/res/values-es/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Informe de error capturado" + "Toca para compartir tu informe de error" + "Los informes de errores contienen datos de los distintos archivos de registro del sistema, incluida información personal y privada. Comparte los informes de errores únicamente con aplicaciones y usuarios en los que confíes." + "Mostrar este mensaje la próxima vez" + diff --git a/packages/Shell/res/values-et/strings.xml b/packages/Shell/res/values-et/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..77881582cc8bc7cb5c5d58b9fd8a9762fe41765c --- /dev/null +++ b/packages/Shell/res/values-et/strings.xml @@ -0,0 +1,24 @@ + + + + + "Kest" + "Veaaruanne jäädvustati" + "Veaaruande jagamiseks puudutage" + "Veaaruanded sisaldavad andmeid erinevatest süsteemi logifailidest, sh isiklikku ja privaatset teavet. Jagage veaaruandeid ainult usaldusväärsete rakenduste ja inimestega." + "Kuva see sõnum järgmisel korral" + diff --git a/packages/Shell/res/values-fa/strings.xml b/packages/Shell/res/values-fa/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..2d2c2236cbc5b90fa4011b9a1cdd6f266220c65e --- /dev/null +++ b/packages/Shell/res/values-fa/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "گزارش اشکال دریاÙت شد" + "جهت اشتراک‌گذاری گزارش اشکال خود لمس کنید" + "گزارش‌های اشکال حاوی داده‌هایی از Ùایل‌های گزارش مختل٠در سیستم هستند، شامل اطلاعات شخصی Ùˆ خصوصی. گزارش‌های اشکال را Ùقط با اÙراد Ùˆ برنامه‌های مورد اعتماد خود به اشتراک بگذارید." + "دÙعه بعد این پیام نشان داده شود" + diff --git a/packages/Shell/res/values-fi/strings.xml b/packages/Shell/res/values-fi/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..ee57279f4a818ff7ce1161b45b8aa6b055699f64 --- /dev/null +++ b/packages/Shell/res/values-fi/strings.xml @@ -0,0 +1,24 @@ + + + + + "Komentotulkki" + "Virheraportti tallennettu" + "Jaa virheraportti koskettamalla tätä" + "Virheraportit sisältävät järjestelmän lokitietoja, ja niihin voi sisältyä henkilökohtaisia ja yksityisiä tietoja. Jaa virheraportteja vain luotettaville sovelluksille ja käyttäjille." + "Näytä tämä viesti seuraavalla kerralla" + diff --git a/packages/Shell/res/values-fr/strings.xml b/packages/Shell/res/values-fr/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..1da6f1f01cdbfd2bff4ed7400d9e2d8e9b8ce93b --- /dev/null +++ b/packages/Shell/res/values-fr/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Rapport de bug enregistré" + "Appuyer ici pour partager votre rapport de bug" + "Les rapports de bug contiennent des données des fichiers journaux du système, y compris des informations personnelles et privées. Ne partagez les rapports de bug qu\'avec les applications et les personnes que vous estimez fiables." + "Afficher ce message la prochaine fois" + diff --git a/packages/Shell/res/values-hi/strings.xml b/packages/Shell/res/values-hi/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..4ea066456e9496afd39c5db0f4fa4f0717e06086 --- /dev/null +++ b/packages/Shell/res/values-hi/strings.xml @@ -0,0 +1,24 @@ + + + + + "शेल" + "बग रिपोरà¥à¤Ÿ कैपà¥à¤šà¤° कर ली गई" + "अपनी बग रिपोरà¥à¤Ÿ साà¤à¤¾ करने के लिठसà¥à¤ªà¤°à¥à¤¶ करें" + "बग रिपोरà¥à¤Ÿ में वà¥à¤¯à¤•à¥à¤¤à¤¿à¤—त और निजी जानकारी सहित, सिसà¥à¤Ÿà¤® की विभिनà¥à¤¨ लॉग फ़ाइलों का डेटा होता है. बग रिपोरà¥à¤Ÿ केवल विशà¥à¤µà¤¸à¤¨à¥€à¤¯ à¤à¤ªà¥à¤²à¤¿à¤•à¥‡à¤¶à¤¨ और वà¥à¤¯à¤•à¥à¤¤à¤¿à¤¯à¥‹à¤‚ से ही साà¤à¤¾ करें." + "यह संदेश अगली बार दिखाà¤à¤‚" + diff --git a/packages/Shell/res/values-hr/strings.xml b/packages/Shell/res/values-hr/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..2c4ea2317d6e53493dccfb5146c6983f39cf9feb --- /dev/null +++ b/packages/Shell/res/values-hr/strings.xml @@ -0,0 +1,24 @@ + + + + + "Ljuska" + "Prijava programske pogreÅ¡ke snimljena je" + "Dodirnite za dijeljenje prijave programske pogreÅ¡ke" + "Prijave programskih pogreÅ¡aka sadržavaju podatke iz razliÄitih datoteka zapisnika sustava, ukljuÄujući osobne i privatne informacije. Prijave programskih pogreÅ¡aka dijelite samo s aplikacijama i osobama koje smatrate pouzdanima." + "Prikaži tu poruku sljedeći put" + diff --git a/packages/Shell/res/values-hu/strings.xml b/packages/Shell/res/values-hu/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..8d684da0855c624555b194bce6115eed14577f1d --- /dev/null +++ b/packages/Shell/res/values-hu/strings.xml @@ -0,0 +1,24 @@ + + + + + "Héj" + "Programhiba-jelentés rögzítve" + "Érintse meg a programhiba-jelentés megosztásához" + "A programhiba-jelentések a rendszer különféle naplófájljaiból származó adatokat tartalmaznak, köztük személyes és magánjellegű információkat is. Csak olyan alkalmazásokkal és személyekkel osszon meg programhiba-jelentéseket, amelyekben vagy akikben megbízik." + "Ãœzenet mutatása legközelebb" + diff --git a/packages/Shell/res/values-in/strings.xml b/packages/Shell/res/values-in/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..8ea2584802eae089aac923ded23b29527e3feea9 --- /dev/null +++ b/packages/Shell/res/values-in/strings.xml @@ -0,0 +1,24 @@ + + + + + "Kerangka" + "Laporan bug tercatat" + "Sentuh untuk membagikan laporan bug Anda" + "Laporan bug berisi data dari berbagai file log sistem, termasuk informasi pribadi dan rahasia. Hanya bagikan laporan bug dengan aplikasi dan orang yang Anda percaya." + "Tampilkan pesan ini lain kali" + diff --git a/packages/Shell/res/values-it/strings.xml b/packages/Shell/res/values-it/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..18a03fe19b47cbb9352fe35c2fa7b2307f94595d --- /dev/null +++ b/packages/Shell/res/values-it/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Segnalazione di bug acquisita" + "Tocca per condividere la segnalazione di bug" + "Le segnalazioni di bug contengono dati da vari file di log del sistema, incluse informazioni personali e private. Condividi le segnalazioni di bug solo con app e persone attendibili." + "Mostra questo messaggio la prossima volta" + diff --git a/packages/Shell/res/values-iw/strings.xml b/packages/Shell/res/values-iw/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..e7715e9ca9d4a259c541d000385ba578dda33b34 --- /dev/null +++ b/packages/Shell/res/values-iw/strings.xml @@ -0,0 +1,24 @@ + + + + + "מעטפת" + "דוח הב××’×™× ×¦×•×œ×" + "×’×¢ כדי לשתף ×ת דוח הב××’×™× ×©×œ×š" + "דוחות על ב××’×™× ×›×•×œ×œ×™× × ×ª×•× ×™× ×ž×§×•×‘×¦×™ היומן ×”×©×•× ×™× ×‘×ž×¢×¨×›×ª, כולל מידע ×ישי ופרטי. שתף דוחות ב××’×™× ×¨×§ ×¢× ×™×™×©×•×ž×™× ×•×× ×©×™× ×©×תה סומך עליה×." + "הצג ×ת ההודעה הזו ×‘×¤×¢× ×”×‘××”" + diff --git a/packages/Shell/res/values-ja/strings.xml b/packages/Shell/res/values-ja/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..88b9c1425d2f1054592e39e78dec5063e1de80be --- /dev/null +++ b/packages/Shell/res/values-ja/strings.xml @@ -0,0 +1,24 @@ + + + + + "シェル" + "ãƒã‚°ãƒ¬ãƒãƒ¼ãƒˆãŒè¨˜éŒ²ã•ã‚Œã¾ã—ãŸ" + "タップã—ã¦ãƒã‚°ãƒ¬ãƒãƒ¼ãƒˆã‚’共有ã™ã‚‹" + "ãƒã‚°ãƒ¬ãƒãƒ¼ãƒˆã«ã¯ã€å€‹äººã®éžå…¬é–‹æƒ…å ±ãªã©ã€ã‚·ã‚¹ãƒ†ãƒ ã®ã•ã¾ã–ã¾ãªãƒ­ã‚°ãƒ•ã‚¡ã‚¤ãƒ«ã®ãƒ‡ãƒ¼ã‚¿ãŒå«ã¾ã‚Œã¾ã™ã€‚共有ã™ã‚‹å ´åˆã¯ä¿¡é ¼ã™ã‚‹ã‚¢ãƒ—リã¨ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã¿ã‚’é¸æŠžã—ã¦ãã ã•ã„。" + "ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’次回も表示ã™ã‚‹" + diff --git a/packages/Shell/res/values-ko/strings.xml b/packages/Shell/res/values-ko/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..d22a8b09e0762e274a514c0f64aba171662b3dff --- /dev/null +++ b/packages/Shell/res/values-ko/strings.xml @@ -0,0 +1,24 @@ + + + + + "ì…¸" + "버그 ì‹ ê³ ì„œ 캡처ë¨" + "버그 신고서를 공유하려면 터치하세요." + "버그 신고서는 ì‹œìŠ¤í…œì˜ ë‹¤ì–‘í•œ 로그 íŒŒì¼ ë°ì´í„°(예: ê°œì¸ ë° ë¹„ê³µê°œ ì •ë³´)를 í¬í•¨í•©ë‹ˆë‹¤. 신뢰할 수 있는 앱과 사용ìžì—게만 버그 신고서를 공유합니다." + "다ìŒì— ì´ ë©”ì‹œì§€ 표시" + diff --git a/packages/Shell/res/values-lt/strings.xml b/packages/Shell/res/values-lt/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..3ac4820a34c31ff348879013e887b0178d0be02d --- /dev/null +++ b/packages/Shell/res/values-lt/strings.xml @@ -0,0 +1,24 @@ + + + + + "Apvalkalas" + "TrikÄių ataskaita užfiksuota" + "Palieskite, kad bendrintumÄ—te trikÄių ataskaitÄ…" + "TrikÄių ataskaitose pateikiami duomenys iÅ¡ įvairių sistemos žurnalo failų, įskaitant asmeninÄ™ ir privaÄiÄ… informacijÄ…. TrikÄių ataskaitas bendrinkite tik su patikimomis programomis ir žmonÄ—mis." + "Rodyti šį praneÅ¡imÄ… kitÄ… kartÄ…" + diff --git a/packages/Shell/res/values-lv/strings.xml b/packages/Shell/res/values-lv/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..3f7f7c1e0f772f5865192bf399e2a5cb8550385b --- /dev/null +++ b/packages/Shell/res/values-lv/strings.xml @@ -0,0 +1,24 @@ + + + + + "Aizsargs" + "Izveidots kļūdu pÄrskats" + "Pieskarieties, lai kopÄ«gotu kļūdu pÄrskatu." + "Kļūdu pÄrskatÄ ir iekļauti dati no dažÄdiem sistÄ“mas žurnÄlfailiem, tostarp personas dati un privÄta informÄcija. Kļūdu pÄrskatus ieteicams kopÄ«got tikai ar uzticamÄm lietotnÄ“m un lietotÄjiem." + "RÄdÄ«t Å¡o ziņojumu nÄkamajÄ reizÄ“" + diff --git a/packages/Shell/res/values-ms/strings.xml b/packages/Shell/res/values-ms/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..8d1e4a228c66ffa8e41aa1e0b3798c10a7c2e554 --- /dev/null +++ b/packages/Shell/res/values-ms/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Laporan pepijat telah ditangkap" + "Sentuh untuk berkongsi laporan pepijat anda" + "Laporan pepijat mengandungi data dari pelbagai fail log sistem, termasuk maklumat peribadi dan sulit. Kongsikan laporan pepijat hanya dengan apl dan orang yang anda percayai." + "Tunjukkan mesej ini pada masa akan datang" + diff --git a/packages/Shell/res/values-nb/strings.xml b/packages/Shell/res/values-nb/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..96d53a633aaf0cafa0a7257b95a8e00443c05831 --- /dev/null +++ b/packages/Shell/res/values-nb/strings.xml @@ -0,0 +1,24 @@ + + + + + "Kommandoliste" + "Feilrapporten er lagret" + "Trykk for Ã¥ dele feilrapporten din" + "Feilrapporter inkluderer data fra systemets forskjellige loggfiler. Dette omfatter personlig og privat informasjon. Du bør bare dele feilrapporter ned apper og folk du stoler pÃ¥." + "Vis denne meldingen neste gang" + diff --git a/packages/Shell/res/values-nl/strings.xml b/packages/Shell/res/values-nl/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..5c32c737f607b48bc4d23a186a352bd4233e472b --- /dev/null +++ b/packages/Shell/res/values-nl/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Foutenrapport vastgelegd" + "Raak aan om uw foutenrapport te delen" + "Foutenrapporten bevatten gegevens uit de verschillende logbestanden van het systeem, waaronder persoonlijke en privégegevens. Deel foutenrapporten alleen met apps en mensen die u vertrouwt." + "Dit bericht de volgende keer weergeven" + diff --git a/packages/Shell/res/values-pl/strings.xml b/packages/Shell/res/values-pl/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..2e28f8dfed21930f33005b2581658c328a296b93 --- /dev/null +++ b/packages/Shell/res/values-pl/strings.xml @@ -0,0 +1,24 @@ + + + + + "PowÅ‚oka" + "Raport o bÅ‚Ä™dach zostaÅ‚ zapisany" + "Kliknij, by udostÄ™pnić raport o bÅ‚Ä™dach" + "Raporty o bÅ‚Ä™dach zawierajÄ… dane z różnych plików dzienników systemu, w tym dane osobowe i prywatne. UdostÄ™pniaj je tylko aplikacjom i osobom, którym ufasz." + "Pokaż ten komunikat nastÄ™pnym razem" + diff --git a/packages/Shell/res/values-pt-rPT/strings.xml b/packages/Shell/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..1c465e0ba647f3070de1cf6243b23ae02d9f09f8 --- /dev/null +++ b/packages/Shell/res/values-pt-rPT/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Relatório de erros capturado" + "Toque para partilhar o relatório de erros" + "Os relatórios de erros incluem dados de vários ficheiros de registo do sistema, nomeadamente informações pessoais e privadas. Partilhe relatórios de erros apenas com aplicações e pessoas fidedignas." + "Mostrar esta mensagem da próxima vez" + diff --git a/packages/Shell/res/values-pt/strings.xml b/packages/Shell/res/values-pt/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..20f4cc9aadfbcfe08ea252ea042367c11002fc64 --- /dev/null +++ b/packages/Shell/res/values-pt/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Relatório de bugs capturado" + "Toque para compartilhar seu relatório de bugs" + "Os relatórios de bugs contêm dados de diversos arquivos de registro do sistema, inclusive informações pessoais e particulares. Compartilhe relatórios de bugs somente com aplicativos e pessoas nos quais você confia." + "Mostrar esta mensagem da próxima vez" + diff --git a/packages/Shell/res/values-ro/strings.xml b/packages/Shell/res/values-ro/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..45c2b0a59acb4d6dd408133247fc4c8c198ae098 --- /dev/null +++ b/packages/Shell/res/values-ro/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Raportul despre erori a fost creat" + "AtingeÈ›i pentru a permite accesul la raportul despre erori" + "Rapoartele despre erori conÈ›in date din diferite fiÈ™iere de jurnal ale sistemului, inclusiv informaÈ›ii private È™i personale. PermiteÈ›i accesul la rapoartele despre erori numai aplicaÈ›iilor È™i persoanelor în care aveÈ›i încredere." + "AfiÈ™aÈ›i acest mesaj data viitoare" + diff --git a/packages/Shell/res/values-ru/strings.xml b/packages/Shell/res/values-ru/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..153e972df6bb824bf2ed9ffdeb5d4f404736a884 --- /dev/null +++ b/packages/Shell/res/values-ru/strings.xml @@ -0,0 +1,24 @@ + + + + + "Оболочка" + "Отчет об ошибках Ñохранен" + "Ðажмите, чтобы отправить отчет об ошибках" + "Отчеты об ошибках Ñодержат данные различных ÑиÑтемных журналов и могут включать личную информацию. Рекомендуем открывать к ним доÑтуп только лицам и приложениÑм, заÑлуживающим доверие." + "Показать Ñто Ñообщение в Ñледующий раз" + diff --git a/packages/Shell/res/values-sk/strings.xml b/packages/Shell/res/values-sk/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..99f36f9ebff360be02723aa4689182b9f4c8dcf3 --- /dev/null +++ b/packages/Shell/res/values-sk/strings.xml @@ -0,0 +1,24 @@ + + + + + "Prostredie" + "Správa o chybách sa zaznamenala" + "Dotykom môžete zdieľaÅ¥ správu o chybách" + "Správy o chybách obsahujú údaje z rôznych súborov denníkov systému vrátane osobných a súkromných informácií. Zdieľajte ich iba s dôveryhodnými aplikáciami a ľuÄmi." + "ZobraziÅ¥ túto správu nabudúce" + diff --git a/packages/Shell/res/values-sl/strings.xml b/packages/Shell/res/values-sl/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..8522d1b3ea65696543242015b3e788684d793f69 --- /dev/null +++ b/packages/Shell/res/values-sl/strings.xml @@ -0,0 +1,24 @@ + + + + + "Lupina" + "PoroÄilo o napaki je posneto" + "Dotaknite se, Äe želite deliti sporoÄilo o napaki z drugimi" + "PoroÄila o napakah vsebujejo podatke iz razliÄnih dnevniÅ¡kih datotek sistema, vkljuÄno z osebnimi in zasebnimi podatki. PoroÄila o napakah delite samo z aplikacijami in ljudmi, ki jim zaupate." + "Pokaži to sporoÄilo naslednjiÄ" + diff --git a/packages/Shell/res/values-sr/strings.xml b/packages/Shell/res/values-sr/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..bef6ff4bc9f499bf027d124550d0936631e0f7d6 --- /dev/null +++ b/packages/Shell/res/values-sr/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Извештај о грешци је Ñнимљен" + "Додирните да биÑте делили извештај о грешци" + "Извештаји о грешкама Ñадрже податке из различитих ÑиÑтемÑких датотека евиденције, укључујући личне и приватне податке. Делите извештаје о грешкама Ñамо Ñа апликацијама и људима у које имате поверења." + "Прикажи ову поруку Ñледећи пут" + diff --git a/packages/Shell/res/values-sv/strings.xml b/packages/Shell/res/values-sv/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..055dc41738f2433f979fcd8410b5231447f8c346 --- /dev/null +++ b/packages/Shell/res/values-sv/strings.xml @@ -0,0 +1,24 @@ + + + + + "Skal" + "Felrapporten har skapats" + "Tryck om du vill dela felrapporten" + "Felrapporter innehÃ¥ller data frÃ¥n systemets olika loggfiler, inklusive personliga och privata uppgifter. Dela bara felrapporter med personer du litar pÃ¥." + "Visa det här meddelandet nästa gÃ¥ng" + diff --git a/packages/Shell/res/values-sw/strings.xml b/packages/Shell/res/values-sw/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..b1d440774c9c156b49f36a46c800c87ea6536c83 --- /dev/null +++ b/packages/Shell/res/values-sw/strings.xml @@ -0,0 +1,24 @@ + + + + + "Ganda" + "Ripoti ya hitilafu imenaswa" + "Gusa ili ushiriki ripoti yako ya hitilafu" + "Ripoti ya hitilafu ina data kutoka kwenye faili za kumbukumbu mbalimbali za mfumo, pamoja na maelezo ya kibinafsi na faragha. Shiriki ripoti ya hitilafu na programu na watu unaowaamini pekee." + "Onyesha ujumbe huu wakati mwingine" + diff --git a/packages/Shell/res/values-th/strings.xml b/packages/Shell/res/values-th/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..b484a426d012274f5babbb6b957bcc0d324c1149 --- /dev/null +++ b/packages/Shell/res/values-th/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "จับภาพรายงานข้อบà¸à¸žà¸£à¹ˆà¸­à¸‡à¹à¸¥à¹‰à¸§" + "à¹à¸•à¸°à¹€à¸žà¸·à¹ˆà¸­à¹à¸Šà¸£à¹Œà¸£à¸²à¸¢à¸‡à¸²à¸™à¸‚้อบà¸à¸žà¸£à¹ˆà¸­à¸‡à¸‚องคุณ" + "รายงานข้อบà¸à¸žà¸£à¹ˆà¸­à¸‡à¸¡à¸µà¸‚้อมูลจาà¸à¹„ฟล์บันทึà¸à¸•à¹ˆà¸²à¸‡à¹† ของระบบ รวมถึงข้อมูลส่วนตัว à¹à¸Šà¸£à¹Œà¸£à¸²à¸¢à¸‡à¸²à¸™à¸‚้อบà¸à¸žà¸£à¹ˆà¸­à¸‡à¸à¸±à¸šà¹à¸­à¸›à¹à¸¥à¸°à¸šà¸¸à¸„คลที่คุณไว้ใจเท่านั้น" + "à¹à¸ªà¸”งข้อความนี้ในครั้งต่อไป" + diff --git a/packages/Shell/res/values-tl/strings.xml b/packages/Shell/res/values-tl/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..20d1b09c32ad4a0859fbf4d2b15c7e6169edbcaf --- /dev/null +++ b/packages/Shell/res/values-tl/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Na-capture ang ulat ng bug" + "Pindutin upang ibahagi ang iyong ulat ng bug" + "Naglalaman ang mga ulat ng bug ng data mula sa iba\'t ibang file ng log ng system, kabilang ang personal at pribadong impormasyon. Magbahagi lang ng mga ulat ng bug sa apps at mga tao na pinagkakatiwalaan mo." + "Ipakita ang mensaheng ito sa susunod" + diff --git a/packages/Shell/res/values-tr/strings.xml b/packages/Shell/res/values-tr/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..56db3fc80742f0b765786a64f28a6d4cbff77504 --- /dev/null +++ b/packages/Shell/res/values-tr/strings.xml @@ -0,0 +1,24 @@ + + + + + "Kabuk" + "Hata raporu kaydedildi" + "Hata raporunuzu paylaÅŸmak için dokunun" + "Hata raporları, kiÅŸisel ve özel bilgiler dahil olmak üzere sistemin çeÅŸitli günlük dosyalarından veriler içerir. Hata raporlarını sadece güvendiÄŸiniz uygulamalar ve kiÅŸilerle paylaşın." + "Bir dahaki sefere bu mesajı göster" + diff --git a/packages/Shell/res/values-uk/strings.xml b/packages/Shell/res/values-uk/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..68e68a8f4d7690ddd8625de78783964bb8bcad80 --- /dev/null +++ b/packages/Shell/res/values-uk/strings.xml @@ -0,0 +1,24 @@ + + + + + "Оболонка" + "Звіт про помилки Ñтворено" + "ТоркнітьÑÑ, щоб надіÑлати звіт про помилки" + "Звіти про помилки міÑÑ‚ÑÑ‚ÑŒ дані з різних файлів журналу ÑиÑтеми, зокрема оÑобиÑÑ‚Ñ– та конфіденційні. ÐадÑилайте звіт про помилки лише тим, кому довірÑєте." + "Показати це Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ñтупного разу" + diff --git a/packages/Shell/res/values-vi/strings.xml b/packages/Shell/res/values-vi/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..ca4fcaa0c98be68750aea9916f65f8544859e129 --- /dev/null +++ b/packages/Shell/res/values-vi/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "Báo cáo lá»—i đã được chụp" + "Chạm để chia sẻ báo cáo lá»—i của bạn" + "Các báo cáo lá»—i chứa dữ liệu từ nhiá»u tệp nhật ký khác nhau của hệ thống, bao gồm cả thông tin cá nhân và riêng tÆ°. Chỉ chia sẻ báo cáo lá»—i vá»›i các ứng dụng và những ngÆ°á»i mà bạn tin tưởng." + "Hiển thị thông báo này vào lần tá»›i" + diff --git a/packages/Shell/res/values-zh-rCN/strings.xml b/packages/Shell/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..f1c385f3a905f20570a3fb54f371835e1cb66261 --- /dev/null +++ b/packages/Shell/res/values-zh-rCN/strings.xml @@ -0,0 +1,24 @@ + + + + + "Shell" + "已抓å–错误报告" + "触摸å³å¯åˆ†äº«æ‚¨çš„错误报告" + "错误报告包å«çš„æ•°æ®æ¥è‡ªäºŽç³»ç»Ÿçš„å„个日志文件,其中包å«ä¸ªäººä¿¡æ¯å’Œéšç§ä¿¡æ¯ã€‚请务必åªä¸Žæ‚¨ä¿¡ä»»çš„应用和用户分享错误报告。" + "下次å†æ˜¾ç¤ºè¿™æ¡è®¯æ¯" + diff --git a/packages/Shell/res/values-zh-rTW/strings.xml b/packages/Shell/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..d3d31403ab2dc468b39139411500f41cb5cb41a1 --- /dev/null +++ b/packages/Shell/res/values-zh-rTW/strings.xml @@ -0,0 +1,24 @@ + + + + + "殼層" + "已擷å–錯誤報告" + "輕觸å³å¯åˆ†äº«æ‚¨çš„錯誤報告" + "錯誤報告的資料來自系統å„個紀錄檔,包括個人和ç§å¯†è³‡è¨Šã€‚請務必åªèˆ‡æ‚¨ä¿¡ä»»çš„應用程å¼å’Œä½¿ç”¨è€…分享錯誤報告。" + "下次ä»é¡¯ç¤ºé€™å‰‡è¨Šæ¯" + diff --git a/packages/Shell/res/values-zu/strings.xml b/packages/Shell/res/values-zu/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..e524b80f18e14b81b2e9e60b6f842d2c32918587 --- /dev/null +++ b/packages/Shell/res/values-zu/strings.xml @@ -0,0 +1,24 @@ + + + + + "I-Shell" + "Umbiko wesiphazamisi uthwetshuliwe" + "Thinta ukuze wabelane ngombiko wakho wesiphazamisi" + "Imibiko yeziphazamisi iqukethe idatha yamafayela wokungena ahlukile wesistimu, afaka ulwazi lomuntu siqu noma lobumfihlo. Yabelana kuphela ngemibiko yeziphazamisi nezinhlelo zokusebenza nabantu obathembayo." + "Bonisa lo mlayezo ngesikhathi esilandelayo" + diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 015c0cc3d54a70abd5dd5fa2215ba85f6bf2cf7d..f8f064a60b7b3997b77a53c9da4e5c135252da5a 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -6,10 +6,11 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-java-files-under, src) \ src/com/android/systemui/EventLogTags.logtags -LOCAL_JAVA_LIBRARIES := services telephony-common +LOCAL_JAVA_LIBRARIES := telephony-common LOCAL_PACKAGE_NAME := SystemUI LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true LOCAL_PROGUARD_FLAG_FILES := proguard.flags diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 66080f304c714964ccc078ed3ef0fa785c0aad7d..09ac2dabcbcb315a0fcf577160d808a47ef5671b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -1,6 +1,7 @@ @@ -23,6 +24,7 @@ + @@ -37,7 +39,6 @@ - @@ -51,7 +52,7 @@ - + @@ -64,6 +65,12 @@ + + + + + + + - - + diff --git a/packages/SystemUI/assets/fonts/AndroidClock.ttf b/packages/SystemUI/assets/fonts/AndroidClock.ttf deleted file mode 100644 index 7b550eeda2d895e0b981d0f794671c3ef526c801..0000000000000000000000000000000000000000 Binary files a/packages/SystemUI/assets/fonts/AndroidClock.ttf and /dev/null differ diff --git a/packages/SystemUI/assets/fonts/AndroidClock2.ttf b/packages/SystemUI/assets/fonts/AndroidClock2.ttf deleted file mode 100644 index a95d5482840c71d54d740e9923a5c1fe1251106a..0000000000000000000000000000000000000000 Binary files a/packages/SystemUI/assets/fonts/AndroidClock2.ttf and /dev/null differ diff --git a/packages/SystemUI/ic_sysbar_internal.psd b/packages/SystemUI/ic_sysbar_internal.psd deleted file mode 100644 index 929c872775cca350428a42f359cdfda083a9b9fd..0000000000000000000000000000000000000000 Binary files a/packages/SystemUI/ic_sysbar_internal.psd and /dev/null differ diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index c886eea1fb64faad544db8dc8312ac670c486fbf..ab45d99dc857caf487705426f5a3a6fddf017b9e 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -1,10 +1,3 @@ --keep class com.android.systemui.statusbar.tablet.TabletStatusBarService { - public void notificationIconsClicked(android.view.View); - public void systemInfoClicked(android.view.View); - public void recentButtonClicked(android.view.View); - public void toggleLightsOut(android.view.View); -} - -keep class com.android.systemui.statusbar.policy.KeyButtonView { public float getDrawingAlpha(); public float getGlowAlpha(); diff --git a/packages/SystemUI/res/anim/heads_up_enter.xml b/packages/SystemUI/res/anim/heads_up_enter.xml new file mode 100644 index 0000000000000000000000000000000000000000..59eef424dd9228210b0190e7a75be6b8b4738882 --- /dev/null +++ b/packages/SystemUI/res/anim/heads_up_enter.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/packages/SystemUI/res/anim/priority_alert_exit.xml b/packages/SystemUI/res/anim/heads_up_exit.xml similarity index 100% rename from packages/SystemUI/res/anim/priority_alert_exit.xml rename to packages/SystemUI/res/anim/heads_up_exit.xml diff --git a/packages/SystemUI/res/anim/priority_alert_enter.xml b/packages/SystemUI/res/anim/priority_alert_enter.xml deleted file mode 100644 index 4fd6a7c9d2f71653ff1ec1c284800c4c1928efa9..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/anim/priority_alert_enter.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/packages/SystemUI/res/anim/wallpaper_recents_launch_from_launcher_enter.xml b/packages/SystemUI/res/anim/wallpaper_recents_launch_from_launcher_enter.xml index 121daae0f6be4178eca9f1f81e54bd4afbe76978..73ae9f2a25ee1fd9885ab1f2040720031cb71060 100644 --- a/packages/SystemUI/res/anim/wallpaper_recents_launch_from_launcher_enter.xml +++ b/packages/SystemUI/res/anim/wallpaper_recents_launch_from_launcher_enter.xml @@ -18,6 +18,7 @@ --> - - - - - - - diff --git a/packages/SystemUI/res/drawable/btn_default_small.xml b/packages/SystemUI/res/drawable/btn_default_small.xml deleted file mode 100644 index 5485ea03027b85636cd5088b99e2405fcdc1c578..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/btn_default_small.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/cling_button_bg.xml b/packages/SystemUI/res/drawable/cling_button_bg.xml deleted file mode 100644 index d175f536c960c8078d5d21f506be03ede517ed8d..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/cling_button_bg.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/hd.xml b/packages/SystemUI/res/drawable/hd.xml deleted file mode 100644 index 73867a29875f36e697006d979f23f4f4eff52f4a..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/hd.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/packages/SystemUI/res/drawable/heads_up_notification_row_bg.xml b/packages/SystemUI/res/drawable/heads_up_notification_row_bg.xml new file mode 100644 index 0000000000000000000000000000000000000000..59d9fcfc7a16cadf8c3256e1df20d72909eedfa2 --- /dev/null +++ b/packages/SystemUI/res/drawable/heads_up_notification_row_bg.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_notify_rotation.xml b/packages/SystemUI/res/drawable/ic_notify_rotation.xml deleted file mode 100644 index 11bc22c366d1fabb0e9649a398cb4d1f37f1229f..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/ic_notify_rotation.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/ic_qs_battery.xml b/packages/SystemUI/res/drawable/ic_qs_battery.xml deleted file mode 100644 index 4e2a265890a9c0378245ca1742140c8e46d62984..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/ic_qs_battery.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/packages/SystemUI/res/drawable/ic_sysbar_ime.xml b/packages/SystemUI/res/drawable/ic_sysbar_ime.xml deleted file mode 100644 index 1accf008c92baa5c7a4400add5cedfe2c959cc28..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/ic_sysbar_ime.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/packages/SystemUI/res/drawable/ic_sysbar_zoom.xml b/packages/SystemUI/res/drawable/ic_sysbar_zoom.xml deleted file mode 100644 index 97d0348f0a3bf4fb4d8f6ba1be3f2c2ebecfd4fd..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/ic_sysbar_zoom.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/intruder_row_bg.xml b/packages/SystemUI/res/drawable/intruder_row_bg.xml deleted file mode 100644 index 1c7c9c454602b6b49ef2f3519e4fc573c162482a..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/intruder_row_bg.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/packages/SystemUI/res/drawable/intruder_window_bg.9.png b/packages/SystemUI/res/drawable/intruder_window_bg.9.png deleted file mode 100644 index caad1691b22633f76690bd39ee01c7f229254e57..0000000000000000000000000000000000000000 Binary files a/packages/SystemUI/res/drawable/intruder_window_bg.9.png and /dev/null differ diff --git a/packages/SystemUI/res/drawable/notify_panel_clock_bg.xml b/packages/SystemUI/res/drawable/notify_panel_clock_bg.xml deleted file mode 100644 index c83d8783786c24a7925c9c57d2d74dadef33fb91..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/notify_panel_clock_bg.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/packages/SystemUI/res/drawable/pocket_drag_bg.xml b/packages/SystemUI/res/drawable/pocket_drag_bg.xml deleted file mode 100644 index 573a7029b6b4c098a0142e138ab3c99bbf926a79..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/pocket_drag_bg.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - diff --git a/packages/SystemUI/res/drawable/qs_sys_battery.xml b/packages/SystemUI/res/drawable/qs_sys_battery.xml deleted file mode 100644 index dd36aa53ee06d37c1fcb3f21a5ede1bf15b2bc4e..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/qs_sys_battery.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/qs_sys_battery_charging.xml b/packages/SystemUI/res/drawable/qs_sys_battery_charging.xml deleted file mode 100644 index cee50811ac70bd2f89ae5655954a96565b324fe9..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/qs_sys_battery_charging.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/stat_sys_battery.xml b/packages/SystemUI/res/drawable/stat_sys_battery.xml deleted file mode 100644 index 744ab93da9af64070ca2d18e8b0df011dcb28785..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/stat_sys_battery.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/stat_sys_battery_charge.xml b/packages/SystemUI/res/drawable/stat_sys_battery_charge.xml deleted file mode 100644 index 6918eb21ed9d813a983a38f76507db7652820997..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/stat_sys_battery_charge.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/SystemUI/res/drawable/stat_sys_gps_acquiring_anim.xml b/packages/SystemUI/res/drawable/stat_sys_gps_acquiring_anim.xml deleted file mode 100644 index 393697cb3508e23526c1742ad57316aded3f6780..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/stat_sys_gps_acquiring_anim.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/packages/SystemUI/res/drawable/stat_sys_roaming_cdma_flash.xml b/packages/SystemUI/res/drawable/stat_sys_roaming_cdma_flash.xml deleted file mode 100644 index 07dc4465ee32097788314f748f8ed35a3d35a931..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/stat_sys_roaming_cdma_flash.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/packages/SystemUI/res/drawable/status_bar_bg.xml b/packages/SystemUI/res/drawable/status_bar_bg.xml deleted file mode 100644 index 403493baf9713a3bbb1deeab8bd7cb6d820b0113..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/status_bar_bg.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - diff --git a/packages/SystemUI/res/drawable/status_bar_expand.xml b/packages/SystemUI/res/drawable/status_bar_expand.xml deleted file mode 100644 index f966920344cdcb5607f373fb686402dcc1378f4c..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/drawable/status_bar_expand.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/packages/SystemUI/res/layout-land/status_bar_help.xml b/packages/SystemUI/res/layout-land/status_bar_help.xml deleted file mode 100644 index a885b86f1a52ad76d10117d3c5449d5f5b9727fd..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/layout-land/status_bar_help.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - -
        " + caption + "" + size + "
        "); - html.append(" "); - html.append(" "); - html.append(" "); - html.append(" "); - html.append(" "); - - appendDiffHtml(html); - - html.append(" "); - html.append(" "); - html.append(" "); - html.append(" "); - html.append(" "); - html.append("
        Expected result:Actual result:
        "); - - return html.toString(); - } - - private void appendDiffHtml(StringBuilder html) { - LinkedList diffs = - new diff_match_patch().diff_main(mExpectedResult, mActualResult); - - diffs = VisualDiffUtils.splitDiffsOnNewline(diffs); - - LinkedList expectedLines = new LinkedList(); - LinkedList expectedLineNums = new LinkedList(); - LinkedList actualLines = new LinkedList(); - LinkedList actualLineNums = new LinkedList(); - - VisualDiffUtils.generateExpectedResultLines(diffs, expectedLineNums, expectedLines); - VisualDiffUtils.generateActualResultLines(diffs, actualLineNums, actualLines); - // TODO: We should use a map for each line number and lines pair. - assert expectedLines.size() == expectedLineNums.size(); - assert actualLines.size() == actualLineNums.size(); - assert expectedLines.size() == actualLines.size(); - - html.append(VisualDiffUtils.getHtml(expectedLineNums, expectedLines, - actualLineNums, actualLines)); - } - - @Override - public TestType getType() { - return TestType.TEXT; - } - - @Override - public void obtainActualResults(WebView webview, Message resultObtainedMsg) { - mResultObtainedMsg = resultObtainedMsg; - Message msg = mHandler.obtainMessage(MSG_DOCUMENT_AS_TEXT); - - /** - * arg1 - should dump top frame as text - * arg2 - should dump child frames as text - */ - msg.arg1 = 1; - msg.arg2 = mDumpChildFramesAsText ? 1 : 0; - WebViewClassic.fromWebView(webview).documentAsText(msg); - } - - @Override - public Bundle getBundle() { - Bundle bundle = new Bundle(); - bundle.putString("expectedTextualResult", mExpectedResult); - bundle.putString("expectedTextualResultPath", mExpectedResultPath); - bundle.putString("actualTextualResult", getActualTextResult()); - bundle.putString("additionalTextOutputString", getAdditionalTextOutputString()); - bundle.putString("relativePath", mRelativePath); - bundle.putBoolean("didTimeOut", mDidTimeOut); - bundle.putString("type", getType().name()); - return bundle; - } - - @Override - public String getRelativePath() { - return mRelativePath; - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java deleted file mode 100644 index d7f731308cdecf22ef4c2c74c4c9eaa973d70ec9..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2; - -import name.fraser.neil.plaintext.diff_match_patch; - -import java.util.LinkedList; - -/** - * Helper methods fo TextResult.getDiffAsHtml() - */ -public class VisualDiffUtils { - - private static final int DONT_PRINT_LINE_NUMBER = -1; - - /** - * Preprocesses the list of diffs so that new line characters appear only at the end of - * diff.text - * - * @param diffs - * @return - * LinkedList of diffs where new line character appears only on the end of - * diff.text - */ - public static LinkedList splitDiffsOnNewline( - LinkedList diffs) { - LinkedList newDiffs = new LinkedList(); - - String[] parts; - int lengthMinusOne; - for (diff_match_patch.Diff diff : diffs) { - parts = diff.text.split("\n", -1); - if (parts.length == 1) { - newDiffs.add(diff); - continue; - } - - lengthMinusOne = parts.length - 1; - for (int i = 0; i < lengthMinusOne; i++) { - newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[i] + "\n")); - } - if (!parts[lengthMinusOne].isEmpty()) { - newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[lengthMinusOne])); - } - } - - return newDiffs; - } - - public static void generateExpectedResultLines(LinkedList diffs, - LinkedList lineNums, LinkedList lines) { - String delSpan = ""; - String eqlSpan = ""; - - String line = ""; - int i = 1; - diff_match_patch.Diff diff; - int size = diffs.size(); - boolean isLastDiff; - for (int j = 0; j < size; j++) { - diff = diffs.get(j); - isLastDiff = j == size - 1; - switch (diff.operation) { - case DELETE: - line = processDiff(diff, lineNums, lines, line, i, delSpan, isLastDiff); - if (line.equals("")) { - i++; - } - break; - - case INSERT: - // If the line is currently empty and this insertion is the entire line, the - // expected line is absent, so it has no line number. - if (diff.text.endsWith("\n") || isLastDiff) { - lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++); - lines.add(line); - line = ""; - } - break; - - case EQUAL: - line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff); - if (line.equals("")) { - i++; - } - break; - } - } - } - - public static void generateActualResultLines(LinkedList diffs, - LinkedList lineNums, LinkedList lines) { - String insSpan = ""; - String eqlSpan = ""; - - String line = ""; - int i = 1; - diff_match_patch.Diff diff; - int size = diffs.size(); - boolean isLastDiff; - for (int j = 0; j < size; j++) { - diff = diffs.get(j); - isLastDiff = j == size - 1; - switch (diff.operation) { - case INSERT: - line = processDiff(diff, lineNums, lines, line, i, insSpan, isLastDiff); - if (line.equals("")) { - i++; - } - break; - - case DELETE: - // If the line is currently empty and deletion is the entire line, the - // actual line is absent, so it has no line number. - if (diff.text.endsWith("\n") || isLastDiff) { - lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++); - lines.add(line); - line = ""; - } - break; - - case EQUAL: - line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff); - if (line.equals("")) { - i++; - } - break; - } - } - } - - /** - * Generate or append a line for a given diff and add it to given collections if necessary. - * It puts diffs in HTML spans. - * - * @param diff - * @param lineNums - * @param lines - * @param line - * @param i - * @param begSpan - * @param forceOutputLine Force the current line to be output - * @return - */ - public static String processDiff(diff_match_patch.Diff diff, LinkedList lineNums, - LinkedList lines, String line, int i, String begSpan, boolean forceOutputLine) { - String endSpan = ""; - String br = " "; - - if (diff.text.endsWith("\n") || forceOutputLine) { - lineNums.add(i); - /** TODO: Think of better way to replace stuff */ - line += begSpan + diff.text.replace(" ", "  ") - + endSpan + br; - lines.add(line); - line = ""; - } else { - line += begSpan + diff.text.replace(" ", "  ") + endSpan; - } - - return line; - } - - public static String getHtml(LinkedList lineNums1, LinkedList lines1, - LinkedList lineNums2, LinkedList lines2) { - StringBuilder html = new StringBuilder(); - int lineNum; - int size = lines1.size(); - for (int i = 0; i < size; i++) { - html.append(""); - - html.append(" "); - lineNum = lineNums1.removeFirst(); - if (lineNum > 0) { - html.append(lineNum); - } - html.append(" "); - - html.append(" "); - html.append(lines1.removeFirst()); - html.append(" "); - - html.append(" "); - - html.append(" "); - lineNum = lineNums2.removeFirst(); - if (lineNum > 0) { - html.append(lineNum); - } - html.append(" "); - - html.append(" "); - html.append(lines2.removeFirst()); - html.append(" "); - - html.append(""); - } - return html.toString(); - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java deleted file mode 100644 index 224509d4023d786abff505360dca238fd41838e0..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.forwarder; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -/** - * The utility class that can setup a socket allowing the device to communicate with remote - * machines through the machine that the device is connected to via adb. - */ -public class AdbUtils { - private static final String LOG_TAG = "AdbUtils"; - - private static final String ADB_OK = "OKAY"; - private static final int ADB_PORT = 5037; - private static final String ADB_HOST = "127.0.0.1"; - private static final int ADB_RESPONSE_SIZE = 4; - - /** - * Creates a new socket that can be configured to serve as a transparent proxy to a - * remote machine. This can be achieved by calling configureSocket() - * - * @return a socket that can be configured to link to remote machine - * @throws IOException - */ - public static Socket createSocket() throws IOException{ - return new Socket(ADB_HOST, ADB_PORT); - } - - /** - * Configures the connection to serve as a transparent proxy to a remote machine. - * The given streams must belong to a socket created by createSocket(). - * - * @param inputStream inputStream of the socket we want to configure - * @param outputStream outputStream of the socket we want to configure - * @param remoteAddress address of the remote machine (as you would type in a browser - * in a machine that the device is connected to via adb) - * @param remotePort port on which to connect - * @return if the configuration suceeded - * @throws IOException - */ - public static boolean configureConnection(InputStream inputStream, OutputStream outputStream, - String remoteAddress, int remotePort) throws IOException { - String cmd = "tcp:" + remotePort + ":" + remoteAddress; - cmd = String.format("%04X", cmd.length()) + cmd; - - byte[] buf = new byte[ADB_RESPONSE_SIZE]; - outputStream.write(cmd.getBytes()); - int read = inputStream.read(buf); - if (read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) { - Log.w(LOG_TAG, "adb cmd failed."); - return false; - } - return true; - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java deleted file mode 100644 index f19cd41fde410b3e5fe5c64608b5301ad3f95c6d..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.forwarder; - -import android.util.Log; - -import com.android.dumprendertree2.FsUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -/** - * Worker class for {@link Forwarder}. A ConnectionHandler will be created once the Forwarder - * accepts an incoming connection, and it will then forward the incoming/outgoing streams to a - * connection already proxied by adb networking (see also {@link AdbUtils}). - */ -public class ConnectionHandler { - - private static final String LOG_TAG = "ConnectionHandler"; - - public static interface OnFinishedCallback { - public void onFinished(); - } - - private class SocketPipeThread extends Thread { - - private InputStream mInputStream; - private OutputStream mOutputStream; - - public SocketPipeThread(InputStream inputStream, OutputStream outputStream) { - mInputStream = inputStream; - mOutputStream = outputStream; - setName("SocketPipeThread: " + getName()); - } - - @Override - public void run() { - byte[] buffer = new byte[4096]; - int length; - while (true) { - try { - if ((length = mInputStream.read(buffer)) < 0) { - break; - } - mOutputStream.write(buffer, 0, length); - } catch (IOException e) { - /** This exception means one of the streams is closed */ - Log.v(LOG_TAG, this.toString(), e); - break; - } - } - - synchronized (mThreadsRunning) { - mThreadsRunning--; - if (mThreadsRunning == 0) { - ConnectionHandler.this.stop(); - mOnFinishedCallback.onFinished(); - } - } - } - - @Override - public String toString() { - return getName(); - } - } - - private Integer mThreadsRunning; - - private Socket mFromSocket, mToSocket; - private SocketPipeThread mFromToPipe, mToFromPipe; - private InputStream mFromSocketInputStream, mToSocketInputStream; - private OutputStream mFromSocketOutputStream, mToSocketOutputStream; - - private int mPort; - private String mRemoteMachineIpAddress; - - private OnFinishedCallback mOnFinishedCallback; - - public ConnectionHandler(String remoteMachineIp, int port, Socket fromSocket, Socket toSocket) - throws IOException { - mRemoteMachineIpAddress = remoteMachineIp; - mPort = port; - - mFromSocket = fromSocket; - mToSocket = toSocket; - - try { - mFromSocketInputStream = mFromSocket.getInputStream(); - mToSocketInputStream = mToSocket.getInputStream(); - mFromSocketOutputStream = mFromSocket.getOutputStream(); - mToSocketOutputStream = mToSocket.getOutputStream(); - AdbUtils.configureConnection(mToSocketInputStream, mToSocketOutputStream, - mRemoteMachineIpAddress, mPort); - } catch (IOException e) { - Log.e(LOG_TAG, "Unable to start ConnectionHandler", e); - closeStreams(); - throw e; - } - - mFromToPipe = new SocketPipeThread(mFromSocketInputStream, mToSocketOutputStream); - mToFromPipe = new SocketPipeThread(mToSocketInputStream, mFromSocketOutputStream); - } - - public void registerOnConnectionHandlerFinishedCallback(OnFinishedCallback callback) { - mOnFinishedCallback = callback; - } - - private void closeStreams() { - FsUtils.closeInputStream(mFromSocketInputStream); - FsUtils.closeInputStream(mToSocketInputStream); - FsUtils.closeOutputStream(mFromSocketOutputStream); - FsUtils.closeOutputStream(mToSocketOutputStream); - } - - public void start() { - /** We have 2 threads running, one for each pipe, that we start here. */ - mThreadsRunning = 2; - mFromToPipe.start(); - mToFromPipe.start(); - } - - public void stop() { - shutdown(mFromSocket); - shutdown(mToSocket); - } - - private void shutdown(Socket socket) { - synchronized (mFromToPipe) { - synchronized (mToFromPipe) { - /** This will stop the while loop in the run method */ - try { - if (!socket.isInputShutdown()) { - socket.shutdownInput(); - } - } catch (IOException e) { - Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e); - } - try { - if (!socket.isOutputShutdown()) { - socket.shutdownOutput(); - } - } catch (IOException e) { - Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e); - } - try { - if (!socket.isClosed()) { - socket.close(); - } - } catch (IOException e) { - Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e); - } - } - } - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java deleted file mode 100644 index ce22fa09022b216622e1035bc79be0bfb499f85e..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.forwarder; - -import android.util.Log; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.HashSet; -import java.util.Set; - -/** - * A port forwarding server. Listens on localhost on specified port and forwards the tcp - * communications to external socket via adb networking proxy. - */ -public class Forwarder extends Thread { - private static final String LOG_TAG = "Forwarder"; - - private int mPort; - private String mRemoteMachineIpAddress; - - private ServerSocket mServerSocket; - - private Set mConnectionHandlers = new HashSet(); - - public Forwarder(int port, String remoteMachineIpAddress) { - mPort = port; - mRemoteMachineIpAddress = remoteMachineIpAddress; - } - - @Override - public void start() { - Log.i(LOG_TAG, "start(): Starting fowarder on port: " + mPort); - - try { - mServerSocket = new ServerSocket(mPort); - } catch (IOException e) { - Log.e(LOG_TAG, "mPort=" + mPort, e); - return; - } - - super.start(); - } - - @Override - public void run() { - while (true) { - Socket localSocket; - try { - localSocket = mServerSocket.accept(); - } catch (IOException e) { - /** This most likely means that mServerSocket is already closed */ - Log.w(LOG_TAG, "mPort=" + mPort, e); - break; - } - - Socket remoteSocket = null; - final ConnectionHandler connectionHandler; - try { - remoteSocket = AdbUtils.createSocket(); - connectionHandler = new ConnectionHandler( - mRemoteMachineIpAddress, mPort, localSocket, remoteSocket); - } catch (IOException exception) { - try { - localSocket.close(); - } catch (IOException e) { - Log.e(LOG_TAG, "mPort=" + mPort, e); - } - if (remoteSocket != null) { - try { - remoteSocket.close(); - } catch (IOException e) { - Log.e(LOG_TAG, "mPort=" + mPort, e); - } - } - continue; - } - - /** - * We have to close the sockets after the ConnectionHandler finishes, so we - * don't get "Too may open files" exception. We also remove the ConnectionHandler - * from the collection to avoid memory issues. - * */ - ConnectionHandler.OnFinishedCallback callback = - new ConnectionHandler.OnFinishedCallback() { - @Override - public void onFinished() { - synchronized (this) { - if (!mConnectionHandlers.remove(connectionHandler)) { - assert false : "removeConnectionHandler(): not in the collection"; - } - } - } - }; - connectionHandler.registerOnConnectionHandlerFinishedCallback(callback); - - synchronized (this) { - mConnectionHandlers.add(connectionHandler); - } - connectionHandler.start(); - } - - synchronized (this) { - for (ConnectionHandler connectionHandler : mConnectionHandlers) { - connectionHandler.stop(); - } - } - } - - public void finish() { - try { - mServerSocket.close(); - } catch (IOException e) { - Log.e(LOG_TAG, "mPort=" + mPort, e); - } - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java deleted file mode 100644 index cff436ffc37e32cd5f5cd91013c00da2d503bc19..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.forwarder; - -import java.net.MalformedURLException; -import java.net.URL; -import android.util.Log; - -import java.util.HashSet; -import java.util.Set; - -/** - * A simple class to start and stop Forwarders running on some ports. - * - * It uses a singleton pattern and is thread safe. - */ -public class ForwarderManager { - private static final String LOG_TAG = "ForwarderManager"; - - /** - * The IP address of the server serving the tests. - */ - private static final String HOST_IP = "127.0.0.1"; - - /** - * We use these ports because other webkit platforms do. They are set up in - * external/webkit/LayoutTests/http/conf/apache2-debian-httpd.conf - */ - public static final int HTTP_PORT = 8000; - public static final int HTTPS_PORT = 8443; - - private static ForwarderManager forwarderManager; - - private Set mForwarders; - private boolean mIsStarted; - - private ForwarderManager() { - mForwarders = new HashSet(2); - mForwarders.add(new Forwarder(HTTP_PORT, HOST_IP)); - mForwarders.add(new Forwarder(HTTPS_PORT, HOST_IP)); - } - - /** - * Returns the main part of the URL with the trailing slash - * - * @param isHttps - * @return - */ - public static final String getHostSchemePort(boolean isHttps) { - int port; - String protocol; - if (isHttps) { - protocol = "https"; - port = HTTPS_PORT; - } else { - protocol = "http"; - port = HTTP_PORT; - } - - URL url = null; - try { - url = new URL(protocol, HOST_IP, port, "/"); - } catch (MalformedURLException e) { - assert false : "isHttps=" + isHttps; - } - - return url.toString(); - } - - public static synchronized ForwarderManager getForwarderManager() { - if (forwarderManager == null) { - forwarderManager = new ForwarderManager(); - } - return forwarderManager; - } - - @Override - public Object clone() throws CloneNotSupportedException { - throw new CloneNotSupportedException(); - } - - public synchronized void start() { - if (mIsStarted) { - Log.w(LOG_TAG, "start(): ForwarderManager already running! NOOP."); - return; - } - - for (Forwarder forwarder : mForwarders) { - forwarder.start(); - } - - mIsStarted = true; - Log.i(LOG_TAG, "ForwarderManager started."); - } - - public synchronized void stop() { - if (!mIsStarted) { - Log.w(LOG_TAG, "stop(): ForwarderManager already stopped! NOOP."); - return; - } - - for (Forwarder forwarder : mForwarders) { - forwarder.finish(); - } - - mIsStarted = false; - Log.i(LOG_TAG, "ForwarderManager stopped."); - } -} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java deleted file mode 100644 index e1d436447f0f6e6f1c9ddab5b6903b0a7ba3294d..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.scriptsupport; - -/** - * Callback used to inform scriptsupport.Starter that everything is finished and - * we can exit - */ -public interface OnEverythingFinishedCallback { - public void onFinished(); -} \ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java deleted file mode 100644 index 78f58d537e39814aac37b8daae9bcfb344ef548f..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.scriptsupport; - -import android.os.Bundle; -import android.test.InstrumentationTestRunner; - -/** - * Extends InstrumentationTestRunner to allow the script to pass arguments to the application - */ -public class ScriptTestRunner extends InstrumentationTestRunner { - String mTestsRelativePath; - - @Override - public void onCreate(Bundle arguments) { - mTestsRelativePath = arguments.getString("path"); - super.onCreate(arguments); - } - - public String getTestsRelativePath() { - return mTestsRelativePath; - } -} \ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java deleted file mode 100644 index 6f41a0f72b7002a55d47fbc79e5e09115b254d17..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.scriptsupport; - -import android.content.Intent; -import android.test.ActivityInstrumentationTestCase2; -import android.util.Log; - -import com.android.dumprendertree2.TestsListActivity; -import com.android.dumprendertree2.forwarder.ForwarderManager; - -/** - * A class which provides methods that can be invoked by a script running on the host machine to - * run the tests. - * - * It starts a TestsListActivity and does not return until all the tests finish executing. - */ -public class Starter extends ActivityInstrumentationTestCase2 { - private static final String LOG_TAG = "Starter"; - private boolean mEverythingFinished; - - public Starter() { - super(TestsListActivity.class); - } - - /** - * This method is called from adb to start executing the tests. It doesn't return - * until everything is finished so that the script can wait for the end if it needs - * to. - */ - public void startLayoutTests() { - ScriptTestRunner runner = (ScriptTestRunner)getInstrumentation(); - String relativePath = runner.getTestsRelativePath(); - - ForwarderManager.getForwarderManager().start(); - - Intent intent = new Intent(); - intent.setClassName("com.android.dumprendertree2", "TestsListActivity"); - intent.setAction(Intent.ACTION_RUN); - intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath); - setActivityIntent(intent); - getActivity().registerOnEverythingFinishedCallback(new OnEverythingFinishedCallback() { - /** This method is safe to call on any thread */ - @Override - public void onFinished() { - synchronized (Starter.this) { - mEverythingFinished = true; - Starter.this.notifyAll(); - } - } - }); - - synchronized (this) { - while (!mEverythingFinished) { - try { - this.wait(); - } catch (InterruptedException e) { - Log.e(LOG_TAG, "startLayoutTests()", e); - } - } - } - - ForwarderManager.getForwarderManager().stop(); - } -} \ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java deleted file mode 100644 index 5de69a724316cd0f9670561feb35ad84122118cd..0000000000000000000000000000000000000000 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (C) 2010 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.dumprendertree2.ui; - -import com.android.dumprendertree2.FileFilter; -import com.android.dumprendertree2.FsUtils; -import com.android.dumprendertree2.TestsListActivity; -import com.android.dumprendertree2.R; -import com.android.dumprendertree2.forwarder.ForwarderManager; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ListActivity; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * An Activity that allows navigating through tests folders and choosing folders or tests to run. - */ -public class DirListActivity extends ListActivity { - - private static final String LOG_TAG = "DirListActivity"; - - /** TODO: This is just a guess - think of a better way to achieve it */ - private static final int MEAN_TITLE_CHAR_SIZE = 13; - - private static final int PROGRESS_DIALOG_DELAY_MS = 200; - - /** Code for the dialog, used in showDialog and onCreateDialog */ - private static final int DIALOG_RUN_ABORT_DIR = 0; - - /** Messages codes */ - private static final int MSG_LOADED_ITEMS = 0; - private static final int MSG_SHOW_PROGRESS_DIALOG = 1; - - private static final CharSequence NO_RESPONSE_MESSAGE = - "No response from host when getting directory contents. Is the host server running?"; - - /** Initialized lazily before first sProgressDialog.show() */ - private static ProgressDialog sProgressDialog; - - private ListView mListView; - - /** This is a relative path! */ - private String mCurrentDirPath; - - /** - * A thread responsible for loading the contents of the directory from sd card - * and sending them via Message to main thread that then loads them into - * ListView - */ - private class LoadListItemsThread extends Thread { - private Handler mHandler; - private String mRelativePath; - - public LoadListItemsThread(String relativePath, Handler handler) { - mRelativePath = relativePath; - mHandler = handler; - } - - @Override - public void run() { - Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS); - msg.obj = getDirList(mRelativePath); - mHandler.sendMessage(msg); - } - } - - /** - * Very simple object to use inside ListView as an item. - */ - private static class ListItem implements Comparable { - private String mRelativePath; - private String mName; - private boolean mIsDirectory; - - public ListItem(String relativePath, boolean isDirectory) { - mRelativePath = relativePath; - mName = new File(relativePath).getName(); - mIsDirectory = isDirectory; - } - - public boolean isDirectory() { - return mIsDirectory; - } - - public String getRelativePath() { - return mRelativePath; - } - - public String getName() { - return mName; - } - - @Override - public int compareTo(ListItem another) { - return mRelativePath.compareTo(another.getRelativePath()); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof ListItem)) { - return false; - } - - return mRelativePath.equals(((ListItem)o).getRelativePath()); - } - - @Override - public int hashCode() { - return mRelativePath.hashCode(); - } - - } - - /** - * A custom adapter that sets the proper icon and label in the list view. - */ - private static class DirListAdapter extends ArrayAdapter { - private Activity mContext; - private ListItem[] mItems; - - public DirListAdapter(Activity context, ListItem[] items) { - super(context, R.layout.dirlist_row, items); - - mContext = context; - mItems = items; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - LayoutInflater inflater = mContext.getLayoutInflater(); - View row = inflater.inflate(R.layout.dirlist_row, null); - - TextView label = (TextView)row.findViewById(R.id.label); - label.setText(mItems[position].getName()); - - ImageView icon = (ImageView)row.findViewById(R.id.icon); - if (mItems[position].isDirectory()) { - icon.setImageResource(R.drawable.folder); - } else { - icon.setImageResource(R.drawable.runtest); - } - - return row; - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - ForwarderManager.getForwarderManager().start(); - - mListView = getListView(); - - mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - ListItem item = (ListItem)parent.getItemAtPosition(position); - - if (item.isDirectory()) { - showDir(item.getRelativePath()); - } else { - /** Run the test */ - runAllTestsUnder(item.getRelativePath()); - } - } - }); - - mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - ListItem item = (ListItem)parent.getItemAtPosition(position); - - if (item.isDirectory()) { - Bundle arguments = new Bundle(1); - arguments.putString("name", item.getName()); - arguments.putString("relativePath", item.getRelativePath()); - showDialog(DIALOG_RUN_ABORT_DIR, arguments); - } else { - /** TODO: Maybe show some info about a test? */ - } - - return true; - } - }); - - /** All the paths are relative to test root dir where possible */ - showDir(""); - } - - private void runAllTestsUnder(String relativePath) { - Intent intent = new Intent(); - intent.setClass(DirListActivity.this, TestsListActivity.class); - intent.setAction(Intent.ACTION_RUN); - intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath); - startActivity(intent); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.gui_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.run_all: - runAllTestsUnder(mCurrentDirPath); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - /** - * Moves to the parent directory if one exists. Does not allow to move above - * the test 'root' directory. - */ - public void onBackPressed() { - File currentDirParent = new File(mCurrentDirPath).getParentFile(); - if (currentDirParent != null) { - showDir(currentDirParent.getPath()); - } else { - showDir(""); - } - } - - /** - * Prevents the activity from recreating on change of orientation. The title needs to - * be recalculated. - */ - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - setTitle(shortenTitle(mCurrentDirPath)); - } - - @Override - protected Dialog onCreateDialog(int id, final Bundle args) { - Dialog dialog = null; - AlertDialog.Builder builder = new AlertDialog.Builder(this); - - switch (id) { - case DIALOG_RUN_ABORT_DIR: - builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " + - args.getString("name")); - builder.setMessage(R.string.dialog_run_abort_dir_msg); - builder.setCancelable(true); - - builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - removeDialog(DIALOG_RUN_ABORT_DIR); - runAllTestsUnder(args.getString("relativePath")); - } - }); - - builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - removeDialog(DIALOG_RUN_ABORT_DIR); - } - }); - - dialog = builder.create(); - dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - removeDialog(DIALOG_RUN_ABORT_DIR); - } - }); - break; - } - - return dialog; - } - - /** - * Loads the contents of dir into the list view. - * - * @param dirPath - * directory to load into list view - */ - private void showDir(String dirPath) { - mCurrentDirPath = dirPath; - - /** Show progress dialog with a delay */ - final Handler delayedDialogHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_SHOW_PROGRESS_DIALOG) { - if (sProgressDialog == null) { - sProgressDialog = new ProgressDialog(DirListActivity.this); - sProgressDialog.setCancelable(false); - sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - sProgressDialog.setTitle(R.string.dialog_progress_title); - sProgressDialog.setMessage(getText(R.string.dialog_progress_msg)); - } - sProgressDialog.show(); - } - } - }; - Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG); - delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS); - - /** Delegate loading contents from SD card to a new thread */ - new LoadListItemsThread(mCurrentDirPath, new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_LOADED_ITEMS) { - setTitle(shortenTitle(mCurrentDirPath)); - delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG); - if (sProgressDialog != null) { - sProgressDialog.dismiss(); - } - if (msg.obj == null) { - Toast.makeText(DirListActivity.this, NO_RESPONSE_MESSAGE, - Toast.LENGTH_LONG).show(); - } else { - setListAdapter(new DirListAdapter(DirListActivity.this, - (ListItem[])msg.obj)); - } - } - } - }).start(); - } - - /** - * TODO: find a neat way to determine number of characters that fit in the title - * bar. - * */ - private String shortenTitle(String title) { - if (title.equals("")) { - return "Tests' root dir:"; - } - int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE; - - if (title.length() > charCount) { - return "..." + title.substring(title.length() - charCount); - } else { - return title; - } - } - - /** - * Return the array with contents of the given directory. - * First it contains the subfolders, then the files. Both sorted - * alphabetically. - * - * The dirPath is relative. - */ - private ListItem[] getDirList(String dirPath) { - List subDirs = new ArrayList(); - List subFiles = new ArrayList(); - - List dirRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, true); - if (dirRelativePaths == null) { - return null; - } - for (String dirRelativePath : dirRelativePaths) { - if (FileFilter.isTestDir(new File(dirRelativePath).getName())) { - subDirs.add(new ListItem(dirRelativePath, true)); - } - } - - List testRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, false); - if (testRelativePaths == null) { - return null; - } - for (String testRelativePath : testRelativePaths) { - if (FileFilter.isTestFile(new File(testRelativePath).getName())) { - subFiles.add(new ListItem(testRelativePath, false)); - } - } - - /** Concatenate the two lists */ - subDirs.addAll(subFiles); - - return subDirs.toArray(new ListItem[subDirs.size()]); - } -} diff --git a/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java b/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java index 5f4f00626059d9d02ded80e60a0dee9f1f3ee11e..309ce34dc87a6df3ad72385c56c5a289ed639bd6 100644 --- a/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java +++ b/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java @@ -21,7 +21,11 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.String; +import java.util.HashMap; +import java.util.Random; +import android.util.ArrayMap; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -142,6 +146,18 @@ public class TestService extends Service { new LoadRecycleLargeBitmapOp(), new LoadSmallScaledBitmapOp(), new LoadLargeScaledBitmapOp(), + new GrowTinyHashMapOp(), + new GrowTinyArrayMapOp(), + new GrowSmallHashMapOp(), + new GrowSmallArrayMapOp(), + new GrowLargeHashMapOp(), + new GrowLargeArrayMapOp(), + new LookupTinyHashMapOp(), + new LookupTinyArrayMapOp(), + new LookupSmallHashMapOp(), + new LookupSmallArrayMapOp(), + new LookupLargeHashMapOp(), + new LookupLargeArrayMapOp(), }; static final int CMD_START_TEST = 1; @@ -1111,4 +1127,196 @@ public class TestService extends Service { mFile.delete(); } } + + static abstract class GenericMapOp extends Op { + final int mSize; + String[] mKeys; + String[] mValues; + + GenericMapOp(String name, String longName, int size) { + super(name, longName); + mSize = size; + } + + void onInit(Context context, boolean foreground) { + mKeys = new String[mSize]; + mValues = new String[mSize]; + Random random = new Random(0); + for (int i=0; i map = new HashMap(); + for (int i=0; i map = new ArrayMap(); + for (int i=0; i map = new HashMap(); + for (int i=0; i map = new ArrayMap(); + for (int i=0; i map = new HashMap(); + for (int i=0; i map = new ArrayMap(); + for (int i=0; i mHashMap; + + LookupSmallHashMapOp() { + super("LookupSmallHashMap", "Lookup items in 100 entry HashMap", 100); + } + + LookupSmallHashMapOp(String name, String longname, int size) { + super(name, longname, size); + } + + void onInit(Context context, boolean foreground) { + super.onInit(context, foreground); + mHashMap = new HashMap(); + for (int i=0; i mArrayMap; + + LookupSmallArrayMapOp() { + super("LookupSmallArrayMap", "Lookup items in 100 entry ArrayMap", 100); + } + + LookupSmallArrayMapOp(String name, String longname, int size) { + super(name, longname, size); + } + + void onInit(Context context, boolean foreground) { + super.onInit(context, foreground); + mArrayMap = new ArrayMap(); + for (int i=0; i + + + + + + + + + + + + + + - + ` diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..df7e3bbf78fad11cb301b7104a582178ca8dc105 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java @@ -0,0 +1,66 @@ +/* + * 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.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; +import com.android.internal.R; + +@SuppressWarnings({"UnusedDeclaration"}) +public class AssetsAtlasActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(new BitmapsView(this)); + } + + static class BitmapsView extends View { + private final Bitmap mBitmap; + + BitmapsView(Context c) { + super(c); + + Drawable d = c.getResources().getDrawable(R.drawable.text_select_handle_left); + mBitmap = ((BitmapDrawable) d).getBitmap(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + final Matrix matrix = new Matrix(); + matrix.setScale(0.5f, 0.5f); + + final Rect src = new Rect(0, 0, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2); + final Rect dst = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); + + canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null); + canvas.translate(0.0f, mBitmap.getHeight()); + canvas.drawBitmap(mBitmap, matrix, null); + canvas.translate(0.0f, mBitmap.getHeight()); + canvas.drawBitmap(mBitmap, src, dst, null); + } + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java index 066e35c3a1c9d1b58e6f1f11c32c3a4ddd39d3f5..fe4d602a62d1cf1078e9cbfb72fceb85b073d3d7 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegion2Activity.java @@ -48,7 +48,7 @@ public class ClipRegion2Activity extends Activity { } public static class RegionView extends FrameLayout { - private final Region mRegion = new Region(); + private Region mRegion = new Region(); private float mClipPosition = 0.0f; public RegionView(Context c) { @@ -69,9 +69,7 @@ public class ClipRegion2Activity extends Activity { canvas.save(); - mRegion.setEmpty(); - mRegion.op(0, 0, getWidth(), getHeight(), - Region.Op.REPLACE); + mRegion.set(0, 0, getWidth(), getHeight()); mRegion.op(getWidth() / 4, getHeight() / 4, 3 * getWidth() / 4, 3 * getHeight() / 4, Region.Op.DIFFERENCE); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java index 13b6129ab1acb5d45426c862d37a25f744cfed01..db6421e1810ef83aa14627189ce1b866b80dfc3d 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java @@ -20,19 +20,15 @@ import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.os.Bundle; -import android.os.Environment; import android.util.DisplayMetrics; import android.view.ContextMenu; import android.view.View; -import android.view.ViewDebug; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; -import java.io.File; - @SuppressWarnings({"UnusedDeclaration"}) public class ListActivity extends Activity { private static final String[] DATA_LIST = { @@ -86,7 +82,7 @@ public class ListActivity extends Activity { ListAdapter adapter = new SimpleListAdapter(this); - ListView list = (ListView) findViewById(R.id.list); + final ListView list = (ListView) findViewById(R.id.list); list.setAdapter(adapter); registerForContextMenu(list); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathOpsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathOpsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..b9927ac08523184757ef86b0e13b9c90213de44a --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathOpsActivity.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 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.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +@SuppressWarnings({"UnusedDeclaration"}) +public class PathOpsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final PathsView view = new PathsView(this); + setContentView(view); + } + + public static class PathsView extends View { + private final Paint mPaint; + private Path[] mPaths; + private float mSize; + + + public PathsView(Context c) { + super(c); + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(Color.RED); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + Path.Op[] ops = Path.Op.values(); + mPaths = new Path[ops.length]; + + mSize = w / (ops.length * 2.0f); + + Path p1 = new Path(); + p1.addRect(0.0f, 0.0f, mSize, mSize, Path.Direction.CW); + + Path p2 = new Path(); + p2.addCircle(mSize, mSize, mSize / 2.0f, Path.Direction.CW); + + for (int i = 0; i < ops.length; i++) { + mPaths[i] = new Path(); + if (!mPaths[i].op(p1, p2, ops[i])) { + Log.d("PathOps", ops[i].name() + " failed!"); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.translate(mSize * 0.2f, getHeight() / 2.0f); + for (Path path : mPaths) { + canvas.drawPath(path, mPaint); + canvas.translate(mSize * 1.8f, 0.0f); + } + } + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java index 9f973117f4df426c54bf4fe85469ec09800c2acc..c1e7f4ad156c895467456a75a3f264ac4edc95fc 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java @@ -88,8 +88,6 @@ public class PathsCacheActivity extends Activity { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - - Log.d("OpenGLRenderer", "Start frame"); canvas.drawARGB(255, 255, 255, 255); @@ -104,6 +102,13 @@ public class PathsCacheActivity extends Activity { canvas.drawPath(mPath, mMediumPaint); canvas.drawPath(mPath, mMediumPaint); + mPath.reset(); + buildPath(mPath); + + canvas.translate(30.0f, 30.0f); + canvas.drawPath(mPath, mMediumPaint); + canvas.drawPath(mPath, mMediumPaint); + canvas.restore(); for (int i = 0; i < mRandom.nextInt(20); i++) { diff --git a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainView.java b/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainView.java index ba09421c0967bf3dc45fb77a8f774bd3c6cf94f9..98cec55b278b87be645ab056ce7847acd6f013fe 100644 --- a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainView.java +++ b/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainView.java @@ -20,7 +20,7 @@ import java.io.Writer; import java.util.ArrayList; import java.util.concurrent.Semaphore; -import android.renderscript.RSTextureView; +import android.renderscript.RSSurfaceView; import android.renderscript.RenderScript; import android.renderscript.RenderScriptGL; @@ -39,7 +39,7 @@ import android.view.SurfaceView; import android.view.KeyEvent; import android.view.MotionEvent; -public class FountainView extends RSTextureView { +public class FountainView extends RSSurfaceView { public FountainView(Context context) { super(context); @@ -49,13 +49,12 @@ public class FountainView extends RSTextureView { private RenderScriptGL mRS; private FountainRS mRender; - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - android.util.Log.e("rs", "onAttachedToWindow"); + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + super.surfaceChanged(holder, format, w, h); if (mRS == null) { RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig(); mRS = createRenderScriptGL(sc); + mRS.setSurface(holder, w, h); mRender = new FountainRS(); mRender.init(mRS, getResources()); } @@ -63,8 +62,6 @@ public class FountainView extends RSTextureView { @Override protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - android.util.Log.e("rs", "onDetachedFromWindow"); if (mRS != null) { mRS = null; destroyRenderScriptGL(); diff --git a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboView.java b/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboView.java index 6e40da346281ec47866cd2bb395cfa26e291de70..86367171d9f6d3d9b29b0165b5f426047dbbbf39 100644 --- a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboView.java +++ b/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboView.java @@ -17,12 +17,13 @@ package com.example.android.rs.fountainfbo; -import android.renderscript.RSTextureView; +import android.renderscript.RSSurfaceView; import android.renderscript.RenderScriptGL; import android.content.Context; +import android.view.SurfaceHolder; import android.view.MotionEvent; -public class FountainFboView extends RSTextureView { +public class FountainFboView extends RSSurfaceView { public FountainFboView(Context context) { super(context); @@ -31,13 +32,12 @@ public class FountainFboView extends RSTextureView { private RenderScriptGL mRS; private FountainFboRS mRender; - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - android.util.Log.e("rs", "onAttachedToWindow"); + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + super.surfaceChanged(holder, format, w, h); if (mRS == null) { RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig(); mRS = createRenderScriptGL(sc); + mRS.setSurface(holder, w, h); mRender = new FountainFboRS(); mRender.init(mRS, getResources()); } diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java deleted file mode 100644 index 5763ad3903c17202f66b619e5333f1d6427717a3..0000000000000000000000000000000000000000 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2011 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.test.tilebenchmark; - -import com.test.tilebenchmark.ProfileActivity.ProfileCallback; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Environment; -import android.test.ActivityInstrumentationTestCase2; -import android.util.Log; -import android.webkit.WebSettings; -import android.widget.Spinner; - -public class PerformanceTest extends - ActivityInstrumentationTestCase2 { - - public static class AnimStat { - double aggVal = 0; - double aggSqrVal = 0; - double count = 0; - } - - private class StatAggregator extends PlaybackGraphs { - private HashMap mDataMap = new HashMap(); - private HashMap mAnimDataMap = new HashMap(); - private int mCount = 0; - - - public void aggregate() { - boolean inAnimTests = mAnimTests != null; - Resources resources = mWeb.getResources(); - String animFramerateString = resources.getString(R.string.animation_framerate); - for (Map.Entry e : mSingleStats.entrySet()) { - String name = e.getKey(); - if (inAnimTests) { - if (name.equals(animFramerateString)) { - // in animation testing phase, record animation framerate and aggregate - // stats, differentiating on values of mAnimTestNr and mDoubleBuffering - String fullName = ANIM_TEST_NAMES[mAnimTestNr] + " " + name; - fullName += mDoubleBuffering ? " tiled" : " webkit"; - - if (!mAnimDataMap.containsKey(fullName)) { - mAnimDataMap.put(fullName, new AnimStat()); - } - AnimStat statVals = mAnimDataMap.get(fullName); - statVals.aggVal += e.getValue(); - statVals.aggSqrVal += e.getValue() * e.getValue(); - statVals.count += 1; - } - } else { - double aggVal = mDataMap.containsKey(name) - ? mDataMap.get(name) : 0; - mDataMap.put(name, aggVal + e.getValue()); - } - } - - if (inAnimTests) { - return; - } - - mCount++; - for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) { - for (int statIndex = 0; statIndex < Stats.length; statIndex++) { - String metricLabel = resources.getString( - Metrics[metricIndex].getLabelId()); - String statLabel = resources.getString( - Stats[statIndex].getLabelId()); - - String label = metricLabel + " " + statLabel; - double aggVal = mDataMap.containsKey(label) ? mDataMap - .get(label) : 0; - - aggVal += mStats[metricIndex][statIndex]; - mDataMap.put(label, aggVal); - } - } - - } - - // build the final bundle of results - public Bundle getBundle() { - Bundle b = new Bundle(); - int count = (0 == mCount) ? Integer.MAX_VALUE : mCount; - for (Map.Entry e : mDataMap.entrySet()) { - b.putDouble(e.getKey(), e.getValue() / count); - } - - for (Map.Entry e : mAnimDataMap.entrySet()) { - String statName = e.getKey(); - AnimStat statVals = e.getValue(); - - double avg = statVals.aggVal/statVals.count; - double stdDev = Math.sqrt((statVals.aggSqrVal / statVals.count) - avg * avg); - - b.putDouble(statName, avg); - b.putDouble(statName + " STD DEV", stdDev); - } - - return b; - } - } - - ProfileActivity mActivity; - ProfiledWebView mWeb; - Spinner mMovementSpinner; - StatAggregator mStats; - - private static final String LOGTAG = "PerformanceTest"; - private static final String TEST_LOCATION = "webkit/page_cycler"; - private static final String URL_PREFIX = "file://"; - private static final String URL_POSTFIX = "/index.html?skip=true"; - private static final int MAX_ITERATIONS = 4; - private static final String SCROLL_TEST_DIRS[] = { - "alexa25_2011" - }; - private static final String ANIM_TEST_DIRS[] = { - "dhtml" - }; - - public PerformanceTest() { - super(ProfileActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - mActivity = getActivity(); - mWeb = (ProfiledWebView) mActivity.findViewById(R.id.web); - mMovementSpinner = (Spinner) mActivity.findViewById(R.id.movement); - mStats = new StatAggregator(); - - // use mStats as a condition variable between the UI thread and - // this(the testing) thread - mActivity.setCallback(new ProfileCallback() { - @Override - public void profileCallback(RunData data) { - mStats.setData(data); - synchronized (mStats) { - mStats.notify(); - } - } - }); - - } - - private boolean loadUrl(final String url) { - try { - Log.d(LOGTAG, "test starting for url " + url); - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mWeb.loadUrl(url); - } - }); - synchronized (mStats) { - mStats.wait(); - } - - mStats.aggregate(); - } catch (InterruptedException e) { - e.printStackTrace(); - return false; - } - return true; - } - - private boolean validTest(String nextTest) { - // if testing animations, test must be in mAnimTests - if (mAnimTests == null) - return true; - - for (String test : mAnimTests) { - if (test.equals(nextTest)) { - return true; - } - } - return false; - } - - private boolean runIteration(String[] testDirs) { - File sdFile = Environment.getExternalStorageDirectory(); - for (String testDirName : testDirs) { - File testDir = new File(sdFile, TEST_LOCATION + "/" + testDirName); - Log.d(LOGTAG, "Testing dir: '" + testDir.getAbsolutePath() - + "', exists=" + testDir.exists()); - - for (File siteDir : testDir.listFiles()) { - if (!siteDir.isDirectory() || !validTest(siteDir.getName())) { - continue; - } - - if (!loadUrl(URL_PREFIX + siteDir.getAbsolutePath() - + URL_POSTFIX)) { - return false; - } - } - } - return true; - } - - private boolean runTestDirs(String[] testDirs) { - for (int i = 0; i < MAX_ITERATIONS; i++) - if (!runIteration(testDirs)) { - return false; - } - return true; - } - - private void pushDoubleBuffering() { - getInstrumentation().runOnMainSync(new Runnable() { - public void run() { - mWeb.setDoubleBuffering(mDoubleBuffering); - } - }); - } - - private void setScrollingTestingMode(final boolean scrolled) { - getInstrumentation().runOnMainSync(new Runnable() { - public void run() { - mMovementSpinner.setSelection(scrolled ? 0 : 2); - } - }); - } - - - private String[] mAnimTests = null; - private int mAnimTestNr = -1; - private boolean mDoubleBuffering = true; - private static final String[] ANIM_TEST_NAMES = { - "slow", "fast" - }; - private static final String[][] ANIM_TESTS = { - {"scrolling", "replaceimages", "layers5", "layers1"}, - {"slidingballs", "meter", "slidein", "fadespacing", "colorfade", - "mozilla", "movingtext", "diagball", "zoom", "imageslide"}, - }; - - private boolean checkMedia() { - String state = Environment.getExternalStorageState(); - - if (!Environment.MEDIA_MOUNTED.equals(state) - && !Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { - Log.d(LOGTAG, "ARG Can't access sd card!"); - // Can't read the SD card, fail and die! - getInstrumentation().sendStatus(1, null); - return false; - } - return true; - } - - public void testMetrics() { - setScrollingTestingMode(true); - if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) { - getInstrumentation().sendStatus(0, mStats.getBundle()); - } else { - getInstrumentation().sendStatus(1, null); - } - } - - public void testMetricsMinimalMemory() { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mWeb.setUseMinimalMemory(true); - } - }); - - setScrollingTestingMode(true); - if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) { - getInstrumentation().sendStatus(0, mStats.getBundle()); - } else { - getInstrumentation().sendStatus(1, null); - } - } - - private boolean runAnimationTests() { - for (int doubleBuffer = 0; doubleBuffer <= 1; doubleBuffer++) { - mDoubleBuffering = doubleBuffer == 1; - pushDoubleBuffering(); - for (mAnimTestNr = 0; mAnimTestNr < ANIM_TESTS.length; mAnimTestNr++) { - mAnimTests = ANIM_TESTS[mAnimTestNr]; - if (!runTestDirs(ANIM_TEST_DIRS)) { - return false; - } - } - } - return true; - } - - public void testAnimations() { - // instead of autoscrolling, load each page until either an timer fires, - // or the animation signals complete via javascript - setScrollingTestingMode(false); - - if (checkMedia() && runAnimationTests()) { - getInstrumentation().sendStatus(0, mStats.getBundle()); - } else { - getInstrumentation().sendStatus(1, null); - } - } -} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java deleted file mode 100644 index 1eb1c003f72bc83d4625a950be272d592c1f9a78..0000000000000000000000000000000000000000 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2011 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.test.tilebenchmark; - -import android.app.Activity; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; -import android.widget.Toast; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; - -/** - * Interface for playing back WebView tile rendering status. Draws viewport and - * states of tiles and statistics for off-line analysis. - */ -public class PlaybackActivity extends Activity { - private static final float SCROLL_SCALER = 0.125f; - - PlaybackView mPlaybackView; - SeekBar mSeekBar; - Button mForward; - Button mBackward; - TextView mFrameDisplay; - - private int mFrame = -1; - private int mFrameMax; - - private class TouchFrameChangeListener extends SimpleOnGestureListener { - float mDist = 0; - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - // aggregate scrolls so that small ones can add up - mDist += distanceY * SCROLL_SCALER; - int intComponent = (int) Math.floor(Math.abs(mDist)); - if (intComponent >= 1) { - int scrollDist = (mDist > 0) ? intComponent : -intComponent; - setFrame(null, mFrame + scrollDist); - mDist -= scrollDist; - } - return super.onScroll(e1, e2, distanceX, distanceY); - } - }; - - private class SeekFrameChangeListener implements OnSeekBarChangeListener { - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, - boolean fromUser) { - setFrame(seekBar, progress); - } - }; - - private class LoadFileTask extends AsyncTask { - @Override - protected RunData doInBackground(String... params) { - RunData data = null; - try { - FileInputStream fis = openFileInput(params[0]); - ObjectInputStream in = new ObjectInputStream(fis); - data = (RunData) in.readObject(); - in.close(); - } catch (IOException ex) { - ex.printStackTrace(); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - } - return data; - } - - @Override - protected void onPostExecute(RunData data) { - if (data == null) { - Toast.makeText(getApplicationContext(), - getResources().getString(R.string.error_no_data), - Toast.LENGTH_LONG).show(); - return; - } - mPlaybackView.setData(data); - - mFrameMax = data.frames.length - 1; - mSeekBar.setMax(mFrameMax); - - setFrame(null, 0); - } - } - - private void setFrame(View changer, int f) { - if (f < 0) { - f = 0; - } else if (f > mFrameMax) { - f = mFrameMax; - } - - if (mFrame == f) { - return; - } - - mFrame = f; - mForward.setEnabled(mFrame != mFrameMax); - mBackward.setEnabled(mFrame != 0); - if (changer != mSeekBar) { - mSeekBar.setProgress(mFrame); - } - mFrameDisplay.setText(Integer.toString(mFrame)); - mPlaybackView.setFrame(mFrame); - }; - - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.playback); - - mPlaybackView = (PlaybackView) findViewById(R.id.playback); - mSeekBar = (SeekBar) findViewById(R.id.seek_bar); - mForward = (Button) findViewById(R.id.forward); - mBackward = (Button) findViewById(R.id.backward); - mFrameDisplay = (TextView) findViewById(R.id.frame_display); - - mForward.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - setFrame(v, mFrame + 1); - } - }); - - mBackward.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - setFrame(v, mFrame - 1); - } - }); - - mSeekBar.setOnSeekBarChangeListener(new SeekFrameChangeListener()); - - mPlaybackView.setOnGestureListener(new TouchFrameChangeListener()); - - new LoadFileTask().execute(ProfileActivity.TEMP_FILENAME); - } -} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java deleted file mode 100644 index 065e86f35629e51a7009298036c08672038cb3a5..0000000000000000000000000000000000000000 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2011 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.test.tilebenchmark; - -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.drawable.ShapeDrawable; - -import com.test.tilebenchmark.RunData.TileData; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; - -public class PlaybackGraphs { - private static final int BAR_WIDTH = PlaybackView.TILE_SCALE * 3; - private static final float CANVAS_SCALE = 0.2f; - private static final double IDEAL_FRAMES = 60; - private static final int LABELOFFSET = 100; - private static Paint whiteLabels; - - private static double viewportCoverage(TileData view, TileData tile) { - if (tile.left < (view.right * view.scale) - && tile.right >= (view.left * view.scale) - && tile.top < (view.bottom * view.scale) - && tile.bottom >= (view.top * view.scale)) { - return 1.0f; - } - return 0.0f; - } - - protected interface MetricGen { - public double getValue(TileData[] frame); - - public double getMax(); - - public int getLabelId(); - }; - - protected static MetricGen[] Metrics = new MetricGen[] { - new MetricGen() { - // framerate graph - @Override - public double getValue(TileData[] frame) { - int renderTimeUS = frame[0].level; - return 1.0e6f / renderTimeUS; - } - - @Override - public double getMax() { - return IDEAL_FRAMES; - } - - @Override - public int getLabelId() { - return R.string.frames_per_second; - } - }, new MetricGen() { - // coverage graph - @Override - public double getValue(TileData[] frame) { - double total = 0, totalCount = 0; - for (int tileID = 1; tileID < frame.length; tileID++) { - TileData data = frame[tileID]; - double coverage = viewportCoverage(frame[0], data); - total += coverage * (data.isReady ? 100 : 0); - totalCount += coverage; - } - if (totalCount == 0) { - return -1; - } - return total / totalCount; - } - - @Override - public double getMax() { - return 100; - } - - @Override - public int getLabelId() { - return R.string.viewport_coverage; - } - } - }; - - protected interface StatGen { - public double getValue(double sortedValues[]); - - public int getLabelId(); - } - - public static double getPercentile(double sortedValues[], double ratioAbove) { - if (sortedValues.length == 0) - return -1; - - double index = ratioAbove * (sortedValues.length - 1); - int intIndex = (int) Math.floor(index); - if (index == intIndex) { - return sortedValues[intIndex]; - } - double alpha = index - intIndex; - return sortedValues[intIndex] * (1 - alpha) - + sortedValues[intIndex + 1] * (alpha); - } - - public static double getMean(double sortedValues[]) { - if (sortedValues.length == 0) - return -1; - - double agg = 0; - for (double val : sortedValues) { - agg += val; - } - return agg / sortedValues.length; - } - - public static double getStdDev(double sortedValues[]) { - if (sortedValues.length == 0) - return -1; - - double agg = 0; - double sqrAgg = 0; - for (double val : sortedValues) { - agg += val; - sqrAgg += val*val; - } - double mean = agg / sortedValues.length; - return Math.sqrt((sqrAgg / sortedValues.length) - (mean * mean)); - } - - protected static StatGen[] Stats = new StatGen[] { - new StatGen() { - @Override - public double getValue(double[] sortedValues) { - return getPercentile(sortedValues, 0.25); - } - - @Override - public int getLabelId() { - return R.string.percentile_25; - } - }, new StatGen() { - @Override - public double getValue(double[] sortedValues) { - return getPercentile(sortedValues, 0.5); - } - - @Override - public int getLabelId() { - return R.string.percentile_50; - } - }, new StatGen() { - @Override - public double getValue(double[] sortedValues) { - return getPercentile(sortedValues, 0.75); - } - - @Override - public int getLabelId() { - return R.string.percentile_75; - } - }, new StatGen() { - @Override - public double getValue(double[] sortedValues) { - return getStdDev(sortedValues); - } - - @Override - public int getLabelId() { - return R.string.std_dev; - } - }, new StatGen() { - @Override - public double getValue(double[] sortedValues) { - return getMean(sortedValues); - } - - @Override - public int getLabelId() { - return R.string.mean; - } - }, - }; - - public PlaybackGraphs() { - whiteLabels = new Paint(); - whiteLabels.setColor(Color.WHITE); - whiteLabels.setTextSize(PlaybackView.TILE_SCALE / 3); - } - - private ArrayList mShapes = new ArrayList(); - protected final double[][] mStats = new double[Metrics.length][Stats.length]; - protected HashMap mSingleStats; - - private void gatherFrameMetric(int metricIndex, double metricValues[], RunData data) { - // create graph out of rectangles, one per frame - int lastBar = 0; - for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) { - TileData frame[] = data.frames[frameIndex]; - int newBar = (int)((frame[0].top + frame[0].bottom) * frame[0].scale / 2.0f); - - MetricGen s = Metrics[metricIndex]; - double absoluteValue = s.getValue(frame); - double relativeValue = absoluteValue / s.getMax(); - relativeValue = Math.min(1,relativeValue); - relativeValue = Math.max(0,relativeValue); - int rightPos = (int) (-BAR_WIDTH * metricIndex); - int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue)); - - ShapeDrawable graphBar = new ShapeDrawable(); - graphBar.getPaint().setColor(Color.BLUE); - graphBar.setBounds(leftPos, lastBar, rightPos, newBar); - - mShapes.add(graphBar); - metricValues[frameIndex] = absoluteValue; - lastBar = newBar; - } - } - - public void setData(RunData data) { - mShapes.clear(); - double metricValues[] = new double[data.frames.length]; - - mSingleStats = data.singleStats; - - if (data.frames.length == 0) { - return; - } - - for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) { - // calculate metric based on list of frames - gatherFrameMetric(metricIndex, metricValues, data); - - // store aggregate statistics per metric (median, and similar) - Arrays.sort(metricValues); - for (int statIndex = 0; statIndex < Stats.length; statIndex++) { - mStats[metricIndex][statIndex] = - Stats[statIndex].getValue(metricValues); - } - } - } - - public void drawVerticalShiftedShapes(Canvas canvas, - ArrayList shapes) { - // Shapes drawn here are drawn relative to the viewRect - Rect viewRect = shapes.get(shapes.size() - 1).getBounds(); - canvas.translate(0, 5 * PlaybackView.TILE_SCALE - viewRect.top); - - for (ShapeDrawable shape : mShapes) { - shape.draw(canvas); - } - for (ShapeDrawable shape : shapes) { - shape.draw(canvas); - } - } - - public void draw(Canvas canvas, ArrayList shapes, - ArrayList strings, Resources resources) { - canvas.scale(CANVAS_SCALE, CANVAS_SCALE); - - canvas.translate(BAR_WIDTH * Metrics.length, 0); - - canvas.save(); - drawVerticalShiftedShapes(canvas, shapes); - canvas.restore(); - - for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) { - String label = resources.getString( - Metrics[metricIndex].getLabelId()); - int xPos = (metricIndex + 1) * -BAR_WIDTH; - int yPos = LABELOFFSET; - canvas.drawText(label, xPos, yPos, whiteLabels); - for (int statIndex = 0; statIndex < Stats.length; statIndex++) { - String statLabel = resources.getString( - Stats[statIndex].getLabelId()).substring(0,3); - label = statLabel + " " + resources.getString( - R.string.format_stat, mStats[metricIndex][statIndex]); - yPos = LABELOFFSET + (1 + statIndex) * PlaybackView.TILE_SCALE - / 2; - canvas.drawText(label, xPos, yPos, whiteLabels); - } - } - for (int stringIndex = 0; stringIndex < strings.size(); stringIndex++) { - int yPos = LABELOFFSET + stringIndex * PlaybackView.TILE_SCALE / 2; - canvas.drawText(strings.get(stringIndex), 0, yPos, whiteLabels); - } - } -} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java deleted file mode 100644 index 5459c1f4de30570943aa633d0469736071d5881f..0000000000000000000000000000000000000000 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2011 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.test.tilebenchmark; - -import android.animation.ArgbEvaluator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.ShapeDrawable; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.GestureDetector.OnGestureListener; -import android.view.MotionEvent; -import android.view.View; - -import com.test.tilebenchmark.RunData.TileData; - -import java.util.ArrayList; - -public class PlaybackView extends View { - public static final int TILE_SCALE = 256; - private static final int INVAL_FLAG = -2; - private static final int INVAL_CYCLE = 250; - - private Paint levelPaint = null, coordPaint = null, goldPaint = null; - private PlaybackGraphs mGraphs; - - private ArrayList mTempShapes = new ArrayList(); - private RunData mProfData = null; - private GestureDetector mGestureDetector = null; - private ArrayList mRenderStrings = new ArrayList(); - - private class TileDrawable extends ShapeDrawable { - TileData tile; - String label; - - public TileDrawable(TileData t, int colorId) { - this.tile = t; - getPaint().setColor(getResources().getColor(colorId)); - if (colorId == R.color.ready_tile - || colorId == R.color.unready_tile) { - - label = (int) (t.left / TILE_SCALE) + ", " - + (int) (t.top / TILE_SCALE); - // ignore scale value for tiles - setBounds(t.left, t.top, - t.right, t.bottom); - } else { - setBounds((int) (t.left * t.scale), - (int) (t.top * t.scale), - (int) (t.right * t.scale), - (int) (t.bottom * t.scale)); - } - } - - @SuppressWarnings("unused") - public void setColor(int color) { - getPaint().setColor(color); - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - if (label != null) { - canvas.drawText(Integer.toString(tile.level), getBounds().left, - getBounds().bottom, levelPaint); - canvas.drawText(label, getBounds().left, - ((getBounds().bottom + getBounds().top) / 2), - coordPaint); - } - } - } - - public PlaybackView(Context context) { - super(context); - init(); - } - - public PlaybackView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public PlaybackView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - public void setOnGestureListener(OnGestureListener gl) { - mGestureDetector = new GestureDetector(getContext(), gl); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mGestureDetector.onTouchEvent(event); - return true; - } - - private void init() { - levelPaint = new Paint(); - levelPaint.setColor(Color.WHITE); - levelPaint.setTextSize(TILE_SCALE / 2); - coordPaint = new Paint(); - coordPaint.setColor(Color.BLACK); - coordPaint.setTextSize(TILE_SCALE / 3); - goldPaint = new Paint(); - goldPaint.setColor(0xffa0e010); - mGraphs = new PlaybackGraphs(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mTempShapes == null || mTempShapes.isEmpty()) { - return; - } - - mGraphs.draw(canvas, mTempShapes, mRenderStrings, getResources()); - invalidate(); // may have animations, force redraw - } - - private String statString(int labelId, int value) { - return getResources().getString(R.string.format_stat_name, - getResources().getString(labelId), value); - } - private String tileString(int formatStringId, TileData t) { - return getResources().getString(formatStringId, - t.left, t.top, t.right, t.bottom); - } - - public int setFrame(int frame) { - if (mProfData == null || mProfData.frames.length == 0) { - return 0; - } - - int readyTiles = 0, unreadyTiles = 0, unplacedTiles = 0, numInvals = 0; - mTempShapes.clear(); - mRenderStrings.clear(); - - // create tile shapes (as they're drawn on bottom) - for (TileData t : mProfData.frames[frame]) { - if (t == mProfData.frames[frame][0]){ - // viewport 'tile', add coords to render strings - mRenderStrings.add(tileString(R.string.format_view_pos, t)); - } else if (t.level != INVAL_FLAG) { - int colorId; - if (t.isReady) { - readyTiles++; - colorId = R.color.ready_tile; - } else { - unreadyTiles++; - colorId = R.color.unready_tile; - } - if (t.left < 0 || t.top < 0) { - unplacedTiles++; - } - mTempShapes.add(new TileDrawable(t, colorId)); - } else { - // inval 'tile', count and add coords to render strings - numInvals++; - mRenderStrings.add(tileString(R.string.format_inval_pos, t)); - } - } - - // create invalidate shapes (drawn above tiles) - int invalId = 0; - for (TileData t : mProfData.frames[frame]) { - if (t.level == INVAL_FLAG && t != mProfData.frames[frame][0]) { - TileDrawable invalShape = new TileDrawable(t, - R.color.inval_region_start); - ValueAnimator tileAnimator = ObjectAnimator.ofInt(invalShape, - "color", - getResources().getColor(R.color.inval_region_start), - getResources().getColor(R.color.inval_region_stop)); - tileAnimator.setDuration(numInvals * INVAL_CYCLE); - tileAnimator.setEvaluator(new ArgbEvaluator()); - tileAnimator.setRepeatCount(ValueAnimator.INFINITE); - tileAnimator.setRepeatMode(ValueAnimator.RESTART); - float delay = (float) (invalId) * INVAL_CYCLE; - tileAnimator.setStartDelay((int) delay); - invalId++; - tileAnimator.start(); - - mTempShapes.add(invalShape); - } - } - - mRenderStrings.add(statString(R.string.ready_tiles, readyTiles)); - mRenderStrings.add(statString(R.string.unready_tiles, unreadyTiles)); - mRenderStrings.add(statString(R.string.unplaced_tiles, unplacedTiles)); - mRenderStrings.add(statString(R.string.number_invalidates, numInvals)); - - // draw view rect (using first TileData object, on top) - TileDrawable viewShape = new TileDrawable(mProfData.frames[frame][0], - R.color.view); - mTempShapes.add(viewShape); - this.invalidate(); - return frame; - } - - public void setData(RunData tileProfilingData) { - mProfData = tileProfilingData; - - mGraphs.setData(mProfData); - } -} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java deleted file mode 100644 index 2e771577a0436399a144aba8f6525d8a0d9621fc..0000000000000000000000000000000000000000 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (C) 2011 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.test.tilebenchmark; - -import android.app.Activity; -import android.content.Intent; -import android.content.Context; -import android.graphics.Bitmap; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.CountDownTimer; -import android.util.Log; -import android.util.Pair; -import android.view.KeyEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; -import android.widget.ToggleButton; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; - -/** - * Interface for profiling the webview's scrolling, with simple controls on how - * to scroll, and what content to load. - */ -public class ProfileActivity extends Activity { - - private static final int TIMED_RECORD_MILLIS = 2000; - - public interface ProfileCallback { - public void profileCallback(RunData data); - } - - public static final String TEMP_FILENAME = "profile.tiles"; - - Button mInspectButton; - ToggleButton mCaptureButton; - Spinner mVelocitySpinner; - Spinner mMovementSpinner; - EditText mUrl; - ProfiledWebView mWeb; - ProfileCallback mCallback; - - LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient(); - AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient(); - TimedLoggingWebViewClient mTimedLoggingWebViewClient = new TimedLoggingWebViewClient(); - - private enum TestingState { - NOT_TESTING, - PRE_TESTING, - START_TESTING, - STOP_TESTING, - SAVED_TESTING - }; - - private class VelocitySelectedListener implements OnItemSelectedListener { - @Override - public void onItemSelected(AdapterView parent, View view, - int position, long id) { - String speedStr = parent.getItemAtPosition(position).toString(); - int speedInt = Integer.parseInt(speedStr); - mWeb.setAutoScrollSpeed(speedInt); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - } - - private class MovementSelectedListener implements OnItemSelectedListener { - @Override - public void onItemSelected(AdapterView parent, View view, - int position, long id) { - String movementStr = parent.getItemAtPosition(position).toString(); - if (movementStr == getResources().getString(R.string.movement_auto_scroll)) { - mWeb.setWebViewClient(mAutoLoggingWebViewClient); - mCaptureButton.setEnabled(false); - mVelocitySpinner.setEnabled(true); - } else if (movementStr == getResources().getString(R.string.movement_manual)) { - mWeb.setWebViewClient(mLoggingWebViewClient); - mCaptureButton.setEnabled(true); - mVelocitySpinner.setEnabled(false); - } else if (movementStr == getResources().getString(R.string.movement_timed)) { - mWeb.setWebViewClient(mTimedLoggingWebViewClient); - mCaptureButton.setEnabled(false); - mVelocitySpinner.setEnabled(false); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - } - - private class LoggingWebViewClient extends WebViewClient { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return false; - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); - mUrl.setText(url); - } - - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - view.requestFocus(); - ((ProfiledWebView)view).onPageFinished(); - } - } - - private class AutoLoggingWebViewClient extends LoggingWebViewClient { - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - startViewProfiling(true); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); - setTestingState(TestingState.PRE_TESTING); - } - } - - private class TimedLoggingWebViewClient extends LoggingWebViewClient { - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - startViewProfiling(false); - - // after a fixed time after page finished, stop testing - new CountDownTimer(TIMED_RECORD_MILLIS, TIMED_RECORD_MILLIS) { - @Override - public void onTick(long millisUntilFinished) { - } - - @Override - public void onFinish() { - mWeb.stopScrollTest(); - } - }.start(); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); - setTestingState(TestingState.PRE_TESTING); - } - } - - private class StoreFileTask extends - AsyncTask, Void, Void> { - - @Override - protected Void doInBackground(Pair... params) { - try { - FileOutputStream fos = openFileOutput(params[0].first, - Context.MODE_PRIVATE); - ObjectOutputStream out = new ObjectOutputStream(fos); - out.writeObject(params[0].second); - out.close(); - } catch (IOException ex) { - ex.printStackTrace(); - } - return null; - } - - @Override - protected void onPostExecute(Void v) { - setTestingState(TestingState.SAVED_TESTING); - } - } - - public void setTestingState(TestingState state) { - switch (state) { - case NOT_TESTING: - mUrl.setBackgroundResource(R.color.background_not_testing); - mInspectButton.setEnabled(true); - mMovementSpinner.setEnabled(true); - break; - case PRE_TESTING: - mInspectButton.setEnabled(false); - mMovementSpinner.setEnabled(false); - break; - case START_TESTING: - mCaptureButton.setChecked(true); - mUrl.setBackgroundResource(R.color.background_start_testing); - mInspectButton.setEnabled(false); - mMovementSpinner.setEnabled(false); - break; - case STOP_TESTING: - mCaptureButton.setChecked(false); - mUrl.setBackgroundResource(R.color.background_stop_testing); - break; - case SAVED_TESTING: - mInspectButton.setEnabled(true); - mMovementSpinner.setEnabled(true); - break; - } - } - - /** auto - automatically scroll. */ - private void startViewProfiling(boolean auto) { - // toggle capture button to indicate capture state to user - mWeb.startScrollTest(mCallback, auto); - setTestingState(TestingState.START_TESTING); - } - - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - mInspectButton = (Button) findViewById(R.id.inspect); - mCaptureButton = (ToggleButton) findViewById(R.id.capture); - mVelocitySpinner = (Spinner) findViewById(R.id.velocity); - mMovementSpinner = (Spinner) findViewById(R.id.movement); - mUrl = (EditText) findViewById(R.id.url); - mWeb = (ProfiledWebView) findViewById(R.id.web); - setCallback(new ProfileCallback() { - @SuppressWarnings("unchecked") - @Override - public void profileCallback(RunData data) { - new StoreFileTask().execute(new Pair( - TEMP_FILENAME, data)); - Log.d("ProfileActivity", "stored " + data.frames.length + " frames in file"); - setTestingState(TestingState.STOP_TESTING); - } - }); - - // Inspect button (opens PlaybackActivity) - mInspectButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(ProfileActivity.this, - PlaybackActivity.class)); - } - }); - - // Velocity spinner - ArrayAdapter adapter = ArrayAdapter.createFromResource( - this, R.array.velocity_array, - android.R.layout.simple_spinner_item); - adapter.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item); - mVelocitySpinner.setAdapter(adapter); - mVelocitySpinner.setOnItemSelectedListener( - new VelocitySelectedListener()); - mVelocitySpinner.setSelection(3); - - // Movement spinner - String content[] = { - getResources().getString(R.string.movement_auto_scroll), - getResources().getString(R.string.movement_manual), - getResources().getString(R.string.movement_timed) - }; - adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, content); - adapter.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item); - mMovementSpinner.setAdapter(adapter); - mMovementSpinner.setOnItemSelectedListener( - new MovementSelectedListener()); - mMovementSpinner.setSelection(0); - - // Capture toggle button - mCaptureButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mCaptureButton.isChecked()) { - startViewProfiling(false); - } else { - mWeb.stopScrollTest(); - } - } - }); - - // Custom profiling WebView - mWeb.init(this); - mWeb.setWebViewClient(new LoggingWebViewClient()); - - // URL text entry - mUrl.setOnEditorActionListener(new OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, - KeyEvent event) { - String url = mUrl.getText().toString(); - mWeb.loadUrl(url); - mWeb.requestFocus(); - return true; - } - }); - - setTestingState(TestingState.NOT_TESTING); - } - - public void setCallback(ProfileCallback callback) { - mCallback = callback; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if ((keyCode == KeyEvent.KEYCODE_BACK) && mWeb.canGoBack()) { - mWeb.goBack(); - return true; - } - return super.onKeyDown(keyCode, event); - } -} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java deleted file mode 100644 index d3b572cc6a69ca3f144b978f6c18270323753a57..0000000000000000000000000000000000000000 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2011 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.test.tilebenchmark; - -import android.content.Context; -import android.os.CountDownTimer; -import android.util.AttributeSet; -import android.util.Log; -import android.webkit.WebSettingsClassic; -import android.webkit.WebView; -import android.webkit.WebViewClassic; - -import java.util.ArrayList; - -import com.test.tilebenchmark.ProfileActivity.ProfileCallback; -import com.test.tilebenchmark.RunData.TileData; - -public class ProfiledWebView extends WebView implements WebViewClassic.PageSwapDelegate { - private static final String LOGTAG = "ProfiledWebView"; - - private int mSpeed; - - private boolean mIsTesting = false; - private boolean mIsScrolling = false; - private ProfileCallback mCallback; - private long mContentInvalMillis; - private static final int LOAD_STALL_MILLIS = 2000; // nr of millis after load, - // before test is forced - - // ignore anim end events until this many millis after load - private static final long ANIM_SAFETY_THRESHOLD = 200; - private long mLoadTime; - private long mAnimationTime; - - public ProfiledWebView(Context context) { - super(context); - } - - public ProfiledWebView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ProfiledWebView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public ProfiledWebView(Context context, AttributeSet attrs, int defStyle, - boolean privateBrowsing) { - super(context, attrs, defStyle, privateBrowsing); - } - - private class JavaScriptInterface { - Context mContext; - - /** Instantiate the interface and set the context */ - JavaScriptInterface(Context c) { - mContext = c; - } - - public void animationComplete() { - mAnimationTime = System.currentTimeMillis(); - } - } - - public void init(Context c) { - WebSettingsClassic settings = getWebViewClassic().getSettings(); - settings.setJavaScriptEnabled(true); - settings.setSupportZoom(true); - settings.setEnableSmoothTransition(true); - settings.setBuiltInZoomControls(true); - settings.setLoadWithOverviewMode(true); - settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does - addJavascriptInterface(new JavaScriptInterface(c), "Android"); - mAnimationTime = 0; - mLoadTime = 0; - } - - public void setUseMinimalMemory(boolean minimal) { - WebSettingsClassic settings = getWebViewClassic().getSettings(); - settings.setProperty("use_minimal_memory", minimal ? "true" : "false"); - } - - public void onPageFinished() { - mLoadTime = System.currentTimeMillis(); - } - - @Override - protected void onDraw(android.graphics.Canvas canvas) { - if (mIsTesting && mIsScrolling) { - if (canScrollVertically(1)) { - scrollBy(0, mSpeed); - } else { - stopScrollTest(); - mIsScrolling = false; - } - } - super.onDraw(canvas); - } - - /* - * Called once the page is loaded to start scrolling for evaluating tiles. - * If autoScrolling isn't set, stop must be called manually. Before - * scrolling, invalidate all content and redraw it, measuring time taken. - */ - public void startScrollTest(ProfileCallback callback, boolean autoScrolling) { - mCallback = callback; - mIsTesting = false; - mIsScrolling = false; - WebSettingsClassic settings = getWebViewClassic().getSettings(); - settings.setProperty("tree_updates", "0"); - - - if (autoScrolling) { - // after a while, force it to start even if the pages haven't swapped - new CountDownTimer(LOAD_STALL_MILLIS, LOAD_STALL_MILLIS) { - @Override - public void onTick(long millisUntilFinished) { - } - - @Override - public void onFinish() { - // invalidate all content, and kick off redraw - Log.d("ProfiledWebView", - "kicking off test with callback registration, and tile discard..."); - getWebViewClassic().discardAllTextures(); - invalidate(); - mIsScrolling = true; - mContentInvalMillis = System.currentTimeMillis(); - } - }.start(); - } else { - mIsTesting = true; - getWebViewClassic().tileProfilingStart(); - } - } - - /* - * Called after the manual contentInvalidateAll, after the tiles have all - * been redrawn. - * From PageSwapDelegate. - */ - @Override - public void onPageSwapOccurred(boolean startAnim) { - if (!mIsTesting && mIsScrolling) { - // kick off testing - mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis; - Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis + "millis"); - mIsTesting = true; - invalidate(); // ensure a redraw so that auto-scrolling can occur - getWebViewClassic().tileProfilingStart(); - } - } - - private double animFramerate() { - WebSettingsClassic settings = getWebViewClassic().getSettings(); - String updatesString = settings.getProperty("tree_updates"); - int updates = (updatesString == null) ? -1 : Integer.parseInt(updatesString); - - long animationTime; - if (mAnimationTime == 0 || mAnimationTime - mLoadTime < ANIM_SAFETY_THRESHOLD) { - animationTime = System.currentTimeMillis() - mLoadTime; - } else { - animationTime = mAnimationTime - mLoadTime; - } - - return updates * 1000.0 / animationTime; - } - - public void setDoubleBuffering(boolean useDoubleBuffering) { - WebSettingsClassic settings = getWebViewClassic().getSettings(); - settings.setProperty("use_double_buffering", useDoubleBuffering ? "true" : "false"); - } - - /* - * Called once the page has stopped scrolling - */ - public void stopScrollTest() { - getWebViewClassic().tileProfilingStop(); - mIsTesting = false; - - if (mCallback == null) { - getWebViewClassic().tileProfilingClear(); - return; - } - - RunData data = new RunData(getWebViewClassic().tileProfilingNumFrames()); - // record the time spent (before scrolling) rendering the page - data.singleStats.put(getResources().getString(R.string.render_millis), - (double)mContentInvalMillis); - - // record framerate - double framerate = animFramerate(); - Log.d(LOGTAG, "anim framerate was "+framerate); - data.singleStats.put(getResources().getString(R.string.animation_framerate), - framerate); - - for (int frame = 0; frame < data.frames.length; frame++) { - data.frames[frame] = new TileData[ - getWebViewClassic().tileProfilingNumTilesInFrame(frame)]; - for (int tile = 0; tile < data.frames[frame].length; tile++) { - int left = getWebViewClassic().tileProfilingGetInt(frame, tile, "left"); - int top = getWebViewClassic().tileProfilingGetInt(frame, tile, "top"); - int right = getWebViewClassic().tileProfilingGetInt(frame, tile, "right"); - int bottom = getWebViewClassic().tileProfilingGetInt(frame, tile, "bottom"); - - boolean isReady = getWebViewClassic().tileProfilingGetInt( - frame, tile, "isReady") == 1; - int level = getWebViewClassic().tileProfilingGetInt(frame, tile, "level"); - - float scale = getWebViewClassic().tileProfilingGetFloat(frame, tile, "scale"); - - data.frames[frame][tile] = data.new TileData(left, top, right, bottom, - isReady, level, scale); - } - } - getWebViewClassic().tileProfilingClear(); - - mCallback.profileCallback(data); - } - - @Override - public void loadUrl(String url) { - mAnimationTime = 0; - mLoadTime = 0; - if (!url.startsWith("http://") && !url.startsWith("file://")) { - url = "http://" + url; - } - super.loadUrl(url); - } - - public void setAutoScrollSpeed(int speedInt) { - mSpeed = speedInt; - } - - public WebViewClassic getWebViewClassic() { - return WebViewClassic.fromWebView(this); - } -} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/RunData.java b/tests/TileBenchmark/src/com/test/tilebenchmark/RunData.java deleted file mode 100644 index 5e48afd2b1033e9daa37cf17ea64df58b398724f..0000000000000000000000000000000000000000 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/RunData.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2011 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.test.tilebenchmark; - -import java.io.Serializable; -import java.util.HashMap; - -public class RunData implements Serializable { - public TileData[][] frames; - public HashMap singleStats = new HashMap(); - - public RunData(int frames) { - this.frames = new TileData[frames][]; - } - - public class TileData implements Serializable { - public int left, top, right, bottom; - public boolean isReady; - public int level; - public float scale; - - public TileData(int left, int top, int right, int bottom, - boolean isReady, int level, float scale) { - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; - this.isReady = isReady; - this.level = level; - this.scale = scale; - } - - public String toString() { - return "Tile (" + left + "," + top + ")->(" - + right + "," + bottom + ")" - + (isReady ? "ready" : "NOTready") + " at scale " + scale; - } - } - -} diff --git a/tests/TransitionTests/Android.mk b/tests/TransitionTests/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..22fa63889261e81220ff369689d30f4bd5ae61c5 --- /dev/null +++ b/tests/TransitionTests/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +# Only compile source java files in this apk. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := TransitionTests + +LOCAL_STATIC_JAVA_LIBRARIES += android-common + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + +# Use the following include to make our test apk. +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/TransitionTests/AndroidManifest.xml b/tests/TransitionTests/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..35e7b6979b7e7a572fc43e850f8406bd72107348 --- /dev/null +++ b/tests/TransitionTests/AndroidManifest.xml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/TransitionTests/res/drawable-hdpi/icon.png b/tests/TransitionTests/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8074c4c571b8cd19e27f4ee5545df367420686d7 Binary files /dev/null and b/tests/TransitionTests/res/drawable-hdpi/icon.png differ diff --git a/tests/TransitionTests/res/drawable-ldpi/icon.png b/tests/TransitionTests/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1095584ec21f71cd0afc9e0993aa2209671b590c Binary files /dev/null and b/tests/TransitionTests/res/drawable-ldpi/icon.png differ diff --git a/tests/TransitionTests/res/drawable-mdpi/icon.png b/tests/TransitionTests/res/drawable-mdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a07c69fa5a0f4da5d5efe96eea12a543154dbab6 Binary files /dev/null and b/tests/TransitionTests/res/drawable-mdpi/icon.png differ diff --git a/tests/TransitionTests/res/drawable-nodpi/arrow_thumbnail.png b/tests/TransitionTests/res/drawable-nodpi/arrow_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..5cae8f25f6539383ff31e63b7d6b1a17b99e2fba Binary files /dev/null and b/tests/TransitionTests/res/drawable-nodpi/arrow_thumbnail.png differ diff --git a/tests/TransitionTests/res/drawable-nodpi/self_portrait_square.jpg b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8cce1c1a352127681a60031012eb02576de78bcd Binary files /dev/null and b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square.jpg differ diff --git a/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_100.jpg b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_100.jpg new file mode 100644 index 0000000000000000000000000000000000000000..26c0a8511e454c10223d5513a5a22912750d1ba7 Binary files /dev/null and b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_100.jpg differ diff --git a/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_200.jpg b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_200.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f18ae5bd1629296bf4a5763cacfc05f2109963c7 Binary files /dev/null and b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_200.jpg differ diff --git a/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_400.jpg b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_400.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3923fd1bb900cb72fdcc5ca4873bffbfac57e3ee Binary files /dev/null and b/tests/TransitionTests/res/drawable-nodpi/self_portrait_square_400.jpg differ diff --git a/tests/TransitionTests/res/layout/activity_login.xml b/tests/TransitionTests/res/layout/activity_login.xml new file mode 100644 index 0000000000000000000000000000000000000000..2aaafc03000f98fe2822d6956c3c8081b7e365da --- /dev/null +++ b/tests/TransitionTests/res/layout/activity_login.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/TransitionTests/res/layout/changing_text_1.xml b/tests/TransitionTests/res/layout/changing_text_1.xml new file mode 100644 index 0000000000000000000000000000000000000000..88086a30215916aa8a6c0e234eda4d15e825eb90 --- /dev/null +++ b/tests/TransitionTests/res/layout/changing_text_1.xml @@ -0,0 +1,39 @@ + + + + +